ChartClient.test.ts 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. import fetchMock from 'fetch-mock';
  20. import {
  21. SupersetClientClass,
  22. SupersetClient,
  23. buildQueryContext,
  24. QueryFormData,
  25. configure as configureTranslation,
  26. ChartClient,
  27. getChartBuildQueryRegistry,
  28. getChartMetadataRegistry,
  29. ChartMetadata,
  30. VizType,
  31. } from '@superset-ui/core';
  32. import { LOGIN_GLOB } from '../fixtures/constants';
  33. import { sankeyFormData } from '../fixtures/formData';
  34. import { SliceIdAndOrFormData } from '../../../src/chart/clients/ChartClient';
  35. configureTranslation();
  36. describe('ChartClient', () => {
  37. let chartClient: ChartClient;
  38. beforeAll(() => {
  39. fetchMock.get(LOGIN_GLOB, { result: '1234' });
  40. SupersetClient.reset();
  41. SupersetClient.configure().init();
  42. });
  43. beforeEach(() => {
  44. chartClient = new ChartClient();
  45. });
  46. afterEach(() => fetchMock.restore());
  47. describe('new ChartClient(config)', () => {
  48. it('creates a client without argument', () => {
  49. expect(chartClient).toBeInstanceOf(ChartClient);
  50. });
  51. it('creates a client with specified config.client', () => {
  52. const customClient = new SupersetClientClass();
  53. chartClient = new ChartClient({ client: customClient });
  54. expect(chartClient).toBeInstanceOf(ChartClient);
  55. expect(chartClient.client).toBe(customClient);
  56. });
  57. });
  58. describe('.loadFormData({ sliceId, formData }, options)', () => {
  59. const sliceId = 123;
  60. it('fetches formData if given only sliceId', () => {
  61. fetchMock.get(
  62. `glob:*/api/v1/form_data/?slice_id=${sliceId}`,
  63. sankeyFormData,
  64. );
  65. return expect(chartClient.loadFormData({ sliceId })).resolves.toEqual(
  66. sankeyFormData,
  67. );
  68. });
  69. it('fetches formData from sliceId and merges with specify formData if both fields are specified', () => {
  70. fetchMock.get(
  71. `glob:*/api/v1/form_data/?slice_id=${sliceId}`,
  72. sankeyFormData,
  73. );
  74. return expect(
  75. chartClient.loadFormData({
  76. sliceId,
  77. formData: {
  78. granularity: 'second',
  79. viz_type: VizType.Bar,
  80. },
  81. }),
  82. ).resolves.toEqual({
  83. ...sankeyFormData,
  84. granularity: 'second',
  85. viz_type: VizType.Bar,
  86. });
  87. });
  88. it('returns promise of formData if only formData was given', () =>
  89. expect(
  90. chartClient.loadFormData({
  91. formData: {
  92. datasource: '1__table',
  93. granularity: 'minute',
  94. viz_type: VizType.Line,
  95. },
  96. }),
  97. ).resolves.toEqual({
  98. datasource: '1__table',
  99. granularity: 'minute',
  100. viz_type: VizType.Line,
  101. }));
  102. it('rejects if none of sliceId or formData is specified', () =>
  103. expect(
  104. chartClient.loadFormData({} as SliceIdAndOrFormData),
  105. ).rejects.toEqual(
  106. new Error('At least one of sliceId or formData must be specified'),
  107. ));
  108. });
  109. describe('.loadQueryData(formData, options)', () => {
  110. it('returns a promise of query data for known chart type', () => {
  111. getChartMetadataRegistry().registerValue(
  112. VizType.WordCloud,
  113. new ChartMetadata({ name: 'Word Cloud', thumbnail: '' }),
  114. );
  115. getChartBuildQueryRegistry().registerValue(
  116. VizType.WordCloud,
  117. (formData: QueryFormData) => buildQueryContext(formData),
  118. );
  119. fetchMock.post('glob:*/api/v1/chart/data', [
  120. {
  121. field1: 'abc',
  122. field2: 'def',
  123. },
  124. ]);
  125. return expect(
  126. chartClient.loadQueryData({
  127. granularity: 'minute',
  128. viz_type: VizType.WordCloud,
  129. datasource: '1__table',
  130. }),
  131. ).resolves.toEqual([
  132. {
  133. field1: 'abc',
  134. field2: 'def',
  135. },
  136. ]);
  137. });
  138. it('returns a promise that rejects for unknown chart type', () =>
  139. expect(
  140. chartClient.loadQueryData({
  141. granularity: 'minute',
  142. viz_type: 'rainbow_3d_pie',
  143. datasource: '1__table',
  144. }),
  145. ).rejects.toEqual(new Error('Unknown chart type: rainbow_3d_pie')));
  146. it('fetches data from the legacy API if ChartMetadata has useLegacyApi=true,', () => {
  147. // note legacy charts do not register a buildQuery function in the registry
  148. getChartMetadataRegistry().registerValue(
  149. 'word_cloud_legacy',
  150. new ChartMetadata({
  151. name: 'Legacy Word Cloud',
  152. thumbnail: '.png',
  153. useLegacyApi: true,
  154. }),
  155. );
  156. fetchMock.post('glob:*/api/v1/chart/data', () =>
  157. Promise.reject(new Error('Unexpected all to v1 API')),
  158. );
  159. fetchMock.post('glob:*/superset/explore_json/', {
  160. field1: 'abc',
  161. field2: 'def',
  162. });
  163. return expect(
  164. chartClient.loadQueryData({
  165. granularity: 'minute',
  166. viz_type: 'word_cloud_legacy',
  167. datasource: '1__table',
  168. }),
  169. ).resolves.toEqual([
  170. {
  171. field1: 'abc',
  172. field2: 'def',
  173. },
  174. ]);
  175. });
  176. });
  177. describe('.loadDatasource(datasourceKey, options)', () => {
  178. it('fetches datasource', () => {
  179. fetchMock.get(
  180. 'glob:*/superset/fetch_datasource_metadata?datasourceKey=1__table',
  181. {
  182. field1: 'abc',
  183. field2: 'def',
  184. },
  185. );
  186. return expect(chartClient.loadDatasource('1__table')).resolves.toEqual({
  187. field1: 'abc',
  188. field2: 'def',
  189. });
  190. });
  191. });
  192. describe('.loadAnnotation(annotationLayer)', () => {
  193. it('returns an empty object if the annotation layer does not require query', () =>
  194. expect(
  195. chartClient.loadAnnotation({
  196. name: 'my-annotation',
  197. }),
  198. ).resolves.toEqual({}));
  199. it('otherwise returns a rejected promise because it is not implemented yet', () =>
  200. expect(
  201. chartClient.loadAnnotation({
  202. name: 'my-annotation',
  203. sourceType: 'abc',
  204. }),
  205. ).rejects.toEqual(new Error('This feature is not implemented yet.')));
  206. });
  207. describe('.loadAnnotations(annotationLayers)', () => {
  208. it('loads multiple annotation layers and combine results', () =>
  209. expect(
  210. chartClient.loadAnnotations([
  211. {
  212. name: 'anno1',
  213. },
  214. {
  215. name: 'anno2',
  216. },
  217. {
  218. name: 'anno3',
  219. },
  220. ]),
  221. ).resolves.toEqual({
  222. anno1: {},
  223. anno2: {},
  224. anno3: {},
  225. }));
  226. it('returns an empty object if input is not an array', () =>
  227. expect(chartClient.loadAnnotations()).resolves.toEqual({}));
  228. it('returns an empty object if input is an empty array', () =>
  229. expect(chartClient.loadAnnotations()).resolves.toEqual({}));
  230. });
  231. describe('.loadChartData({ sliceId, formData })', () => {
  232. const sliceId = 10120;
  233. it('loadAllDataNecessaryForAChart', () => {
  234. fetchMock.get(`glob:*/api/v1/form_data/?slice_id=${sliceId}`, {
  235. granularity: 'minute',
  236. viz_type: VizType.Line,
  237. datasource: '1__table',
  238. color: 'living-coral',
  239. });
  240. fetchMock.get(
  241. 'glob:*/superset/fetch_datasource_metadata?datasourceKey=1__table',
  242. {
  243. name: 'transactions',
  244. schema: 'staging',
  245. },
  246. );
  247. fetchMock.post('glob:*/api/v1/chart/data', {
  248. lorem: 'ipsum',
  249. dolor: 'sit',
  250. amet: true,
  251. });
  252. getChartMetadataRegistry().registerValue(
  253. VizType.Line,
  254. new ChartMetadata({ name: 'Line', thumbnail: '.gif' }),
  255. );
  256. getChartBuildQueryRegistry().registerValue(
  257. VizType.Line,
  258. (formData: QueryFormData) => buildQueryContext(formData),
  259. );
  260. return expect(
  261. chartClient.loadChartData({
  262. sliceId,
  263. }),
  264. ).resolves.toEqual({
  265. annotationData: {},
  266. datasource: {
  267. name: 'transactions',
  268. schema: 'staging',
  269. },
  270. formData: {
  271. granularity: 'minute',
  272. viz_type: VizType.Line,
  273. datasource: '1__table',
  274. color: 'living-coral',
  275. },
  276. queriesData: [
  277. {
  278. lorem: 'ipsum',
  279. dolor: 'sit',
  280. amet: true,
  281. },
  282. ],
  283. });
  284. });
  285. });
  286. });