diff --git a/packages/client/package.json b/packages/client/package.json index 46c60955..3fed04a7 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "client", - "version": "1.0.1", + "version": "1.1.0", "description": "The client lib for the Referee UX", "author": "Justin Field ", "homepage": "https://example.com/dashboard/", @@ -70,6 +70,7 @@ "@types/react-syntax-highlighter": "^13.5.0", "@types/uuid": "^8.3.0", "@types/yup": "^0.26.12", + "axios-mock-adapter": "^1.19.0", "node-sass": "^4.12.0" }, "repository": { diff --git a/packages/client/src/components/App.tsx b/packages/client/src/components/App.tsx index 1d3deb20..9fd0190d 100755 --- a/packages/client/src/components/App.tsx +++ b/packages/client/src/components/App.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import * as H from 'history'; import { Provider } from 'mobx-react'; import { BrowserRouter, Route } from 'react-router-dom'; diff --git a/packages/client/src/components/PageViewTracker.tsx b/packages/client/src/components/PageViewTracker.tsx index 02d3fb01..d3714900 100644 --- a/packages/client/src/components/PageViewTracker.tsx +++ b/packages/client/src/components/PageViewTracker.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { RouteComponentProps, RouterProps } from 'react-router'; +import { RouteComponentProps } from 'react-router'; import { EVENT, trackEvent } from '../util/MetricUtils'; import { ofNullable, trimToEmpty } from '../util/OptionalUtils'; diff --git a/packages/client/src/components/config/LoadCanaryConfigNavItem.tsx b/packages/client/src/components/config/LoadCanaryConfigNavItem.tsx index 12e8ce60..f0643ae9 100644 --- a/packages/client/src/components/config/LoadCanaryConfigNavItem.tsx +++ b/packages/client/src/components/config/LoadCanaryConfigNavItem.tsx @@ -49,6 +49,7 @@ export default class LoadCanaryConfigNavItem extends React.Component { source: 'file', name: safeGet(() => unvalidatedJsonObject.name).orElse('UNKNOWN') }); + alert('Successfully loaded canary config from file'); } render(): React.ReactNode { diff --git a/packages/client/src/components/shared/IndividualMetricView.test.tsx b/packages/client/src/components/shared/IndividualMetricView.test.tsx index 7c5eb59b..daa7977a 100644 --- a/packages/client/src/components/shared/IndividualMetricView.test.tsx +++ b/packages/client/src/components/shared/IndividualMetricView.test.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { filterNansFromData, calculateTimeLabels } from './IndividualMetricView'; import { MetricSetPair } from '../../domain/Kayenta'; diff --git a/packages/client/src/components/shared/IndividualMetricView.tsx b/packages/client/src/components/shared/IndividualMetricView.tsx index d130315a..f075a5b2 100644 --- a/packages/client/src/components/shared/IndividualMetricView.tsx +++ b/packages/client/src/components/shared/IndividualMetricView.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { observer } from 'mobx-react'; import './IndividualMetricView.scss'; import { Line } from 'react-chartjs-2'; @@ -44,7 +44,7 @@ export const calculateTimeLabels = ( let timeLabels: number[] = []; if (metricSourceType === SIGNAL_FX_SERVICE_TYPE) { - const { controlTimeLabels, experimentTimeLabels } = mapIfPresent( + const { controlTimeLabels } = mapIfPresent( Optional.ofNullable(metricSourceIntegrations()[metricSourceType].graphData), graphDataMapper => { return safeGet(() => graphDataMapper(metricSetPairsByIdMap[selectedMetric].attributes)); diff --git a/packages/client/src/metricSources/SignalFx/SignalFxMetricModal.tsx b/packages/client/src/metricSources/SignalFx/SignalFxMetricModal.tsx index 7ac0c768..135842c2 100644 --- a/packages/client/src/metricSources/SignalFx/SignalFxMetricModal.tsx +++ b/packages/client/src/metricSources/SignalFx/SignalFxMetricModal.tsx @@ -197,6 +197,7 @@ export default class SignalFxMetricModal extends AbstractMetricModal Using SignalFlow {' '} diff --git a/packages/client/src/metricSources/SignalFx/index.test.tsx b/packages/client/src/metricSources/SignalFx/index.test.tsx index caadbcbf..5ec1d4c7 100644 --- a/packages/client/src/metricSources/SignalFx/index.test.tsx +++ b/packages/client/src/metricSources/SignalFx/index.test.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { lifetimeMillis, scale, timeLabels } from './index'; import Optional from 'optional-js'; diff --git a/packages/client/src/metricSources/index.test.tsx b/packages/client/src/metricSources/index.test.tsx index 4331585a..a1c3a3d0 100644 --- a/packages/client/src/metricSources/index.test.tsx +++ b/packages/client/src/metricSources/index.test.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { defaultGraphDataMapper } from './index'; it('generates default graph labels with populated parameters', () => { diff --git a/packages/client/src/metricSources/index.tsx b/packages/client/src/metricSources/index.tsx index 16c1c385..847dd2c0 100644 --- a/packages/client/src/metricSources/index.tsx +++ b/packages/client/src/metricSources/index.tsx @@ -1,4 +1,4 @@ -import { CanaryMetricSetQueryConfig, MetricSetPairAttributes } from '../domain/Kayenta'; +import { CanaryMetricSetQueryConfig } from '../domain/Kayenta'; import { MetricSourceIntegration } from './MetricSourceIntegration'; import NewRelic from './NewRelic'; import SignalFx from './SignalFx'; diff --git a/packages/client/src/services/DocsService.ts b/packages/client/src/services/DocsService.ts index 4b60afd8..e77bebbd 100644 --- a/packages/client/src/services/DocsService.ts +++ b/packages/client/src/services/DocsService.ts @@ -55,8 +55,8 @@ export default class DocsService { async fetchAndUpdateToc(): Promise { if (docsStore.tableOfContents == null) { try { - const response = await axios.get(`${process.env.PUBLIC_URL}/docs/table-of-contents.yaml`); - const tocData: any = yaml.load(response.data); + const data = await this.fetchToc(); + const tocData: any = yaml.load(data); const validationResults = validateToc(tocData); // TODO wire up error component rather than crash entire site. @@ -73,6 +73,15 @@ export default class DocsService { } } + async fetchToc(): Promise { + try { + const response = await axios.get(`${process.env.PUBLIC_URL}/docs/table-of-contents.yaml`); + return response.data; + } catch (e) { + throw e; + } + } + fetchAndUpdateDocContent(path: string): void { const resolvedPath = OptionalUtils.trimToEmpty(path).orElse( OptionalUtils.safeGet(() => docsStore.tableOfContents!.home).orElse('index') @@ -84,11 +93,20 @@ export default class DocsService { log.debug(`${markdown} - in mem cache hit, using that`); docsStore.updateContent(docCache.get(markdown) as string); } else { - axios.get(`${process.env.PUBLIC_URL}/docs/${markdown}`).then(response => { - const renderedContent = marked.parse(Optional.ofNullable(response.data).orElse(' ')); + this.fetchDocContent(markdown).then(data => { + const renderedContent = marked.parse(Optional.ofNullable(data).orElse(' ')); docCache.set(markdown, renderedContent); docsStore.updateContent(renderedContent); }); } } + + async fetchDocContent(markdown): Promise { + try { + const response = await axios.get(`${process.env.PUBLIC_URL}/docs/${markdown}`); + return response.data; + } catch (e) { + throw e; + } + } } diff --git a/packages/client/src/services/FetchCanaryResultsService.ts b/packages/client/src/services/FetchCanaryResultsService.ts index f3c33bb7..ba8ee37a 100644 --- a/packages/client/src/services/FetchCanaryResultsService.ts +++ b/packages/client/src/services/FetchCanaryResultsService.ts @@ -5,25 +5,29 @@ import { CanaryExecutionStatusResponse } from '../domain/Kayenta'; const { configEditorStore, canaryExecutorStore, reportStore } = stores; let response: CanaryExecutionStatusResponse | any = {}; -const SUCCESS = 'succeeded'; export default class FetchCanaryResultsService { async pollForCanaryExecutionComplete(canaryExecutionId: string): Promise { kayentaApiService.fetchCredentials().then(data => canaryExecutorStore.setKayentaCredentials(data)); canaryExecutorStore.setCanaryExecutionId(canaryExecutionId); - do { - const data = async () => { - await delay(1000); - response = await kayentaApiService.fetchCanaryExecutionStatusResponse(canaryExecutorStore.canaryExecutionId); - canaryExecutorStore.updateStageStatus(response.stageStatus); - }; - await data(); - } while (!response.complete); - + response = await this.pollForResponse(); reportStore.updateFromCanaryResponse(response); configEditorStore.setCanaryConfigObject(response.config); canaryExecutorStore.setCanaryExecutionRequestObject(response.canaryExecutionRequest); canaryExecutorStore.updateResultsRequestComplete(); } + + async pollForResponse(): Promise { + const data = async () => { + await delay(1000); + response = await kayentaApiService.fetchCanaryExecutionStatusResponse(canaryExecutorStore.canaryExecutionId); + canaryExecutorStore.updateStageStatus(response.stageStatus); + }; + + do { + await data(); + } while (!response.complete); + return response; + } } diff --git a/packages/client/src/services/KayentaApiService.ts b/packages/client/src/services/KayentaApiService.ts index 6c5ec8dc..309fdc15 100644 --- a/packages/client/src/services/KayentaApiService.ts +++ b/packages/client/src/services/KayentaApiService.ts @@ -11,7 +11,7 @@ import { MetricSetPair } from '../domain/Kayenta'; -const kayentaClient = axios.create({ +export const kayentaClient = axios.create({ baseURL: `${window.location.origin}/kayenta/`, timeout: 10000 }); @@ -23,6 +23,7 @@ export default class KayentaApiService { return response.data; } catch (e) { log.error('Failed to fetch account metadata from Kayenta', e); + alert('Failed to fetch account metadata from Kayenta'); throw e; } } diff --git a/packages/client/src/services/LoadCanaryConfigService.ts b/packages/client/src/services/LoadCanaryConfigService.ts index 18a28c5d..4afc7a10 100644 --- a/packages/client/src/services/LoadCanaryConfigService.ts +++ b/packages/client/src/services/LoadCanaryConfigService.ts @@ -12,11 +12,11 @@ export default class LoadCanaryConfigService { let contents: string = '{}'; try { - // @ts-ignore contents = await navigator.clipboard.readText(); const unvalidatedJsonObject = JSON.parse(contents); // TODO validate object with yup trackEvent(EVENT.LOAD_CONFIG, { source: 'clipboard' }); + alert('Successfully loaded canary config from clipboard'); return unvalidatedJsonObject as CanaryConfig; } catch (e) { log.error('Failed to read and deserialize clipboard contents -', e); @@ -27,22 +27,50 @@ export default class LoadCanaryConfigService { async loadCanaryFromTemplate(): Promise { const currentUrl = window.location.href; - const pattern = new RegExp(/.+template=([\w.-]+)(.+)?/); - let templateName = ''; - if (currentUrl.match(pattern) !== null) { - const templateNameMatcher = pattern.exec(currentUrl); - templateName = templateNameMatcher ? templateNameMatcher[1] : ''; + if (this.isBlankConfig(currentUrl)) { + return; + } + try { + const templateName = this.getTemplateName(currentUrl); const fileName = `${templateName}.json`; - axios.get(`${process.env.PUBLIC_URL}/templates/${fileName}`).then(response => { - configEditorStore.setCanaryConfigObject(response.data); - trackEvent(EVENT.LOAD_CONFIG, { - source: 'template', - template: templateName, - name: safeGet(() => response.data.name).orElse('UNKNOWN') - }); + const templateCanaryConfig = await this.fetchTemplateContent(fileName); + configEditorStore.setCanaryConfigObject(templateCanaryConfig); + trackEvent(EVENT.LOAD_CONFIG, { + source: 'template', + template: templateName, + name: safeGet(() => templateCanaryConfig.name).orElse('UNKNOWN') }); + } catch (e) { + log.error('Failed to fetch canary config template'); + alert(`Failed to fetch canary config template:\n${e}`); + } + } + + isBlankConfig(url: string): boolean { + const pattern = /.+config\/edit$/; + return url.match(pattern) !== null; + } + + getTemplateName(url: string): string { + const pattern = /.+template=([\w.-]+)(.+)?/; + + if (url.match(pattern) == null) { + throw new Error(`Template URL does not match pattern: ${url}`); + } + + const templateNameMatcher = pattern.exec(url); + return templateNameMatcher ? templateNameMatcher[1] : ''; + } + + async fetchTemplateContent(fileName: string): Promise { + try { + const response = await axios.get(`${process.env.PUBLIC_URL}/templates/${fileName}`); + return response.data; + } catch (e) { + log.error(`Failed to fetch canary config template: ${fileName}`, e); + throw e; } } } diff --git a/packages/client/src/services/TemplatesService.ts b/packages/client/src/services/TemplatesService.ts deleted file mode 100644 index 4b4653d7..00000000 --- a/packages/client/src/services/TemplatesService.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { stores } from '../stores'; -import axios from 'axios'; - -const { configEditorStore } = stores; - -export default class TemplatesService { - async fetchAndUpdateTemplateContent(): Promise { - const currentUrl = window.location.href; - const pattern = new RegExp(/.+template=([\w.-]+)(.+)?/); - let templateName = ''; - - if (currentUrl.match(pattern) !== null) { - const templateNameMatcher = pattern.exec(currentUrl); - templateName = templateNameMatcher ? templateNameMatcher[1] : ''; - const fileName = `${templateName}.json`; - - axios.get(`${process.env.PUBLIC_URL}/templates/${fileName}`).then(response => { - configEditorStore.setCanaryConfigObject(response.data); - }); - } - } -} diff --git a/packages/client/src/services/__tests__/DocsService.test.ts b/packages/client/src/services/__tests__/DocsService.test.ts new file mode 100644 index 00000000..252df36f --- /dev/null +++ b/packages/client/src/services/__tests__/DocsService.test.ts @@ -0,0 +1,41 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios'; +import DocsService from '../DocsService'; + +describe('DocsService', () => { + let httpMock; + let docsService; + + beforeEach(() => { + httpMock = new MockAdapter(axios); + docsService = new DocsService(); + }); + + it('should fetch toc', async () => { + const expectedData = { response: true }; + httpMock.onGet(`${process.env.PUBLIC_URL}/docs/table-of-contents.yaml`).reply(200, expectedData); + const actualData = await docsService.fetchToc(); + expect(actualData).toEqual(expectedData); + }); + + it('should not fetch toc and throw error', async () => { + const expectedData = { response: true }; + httpMock.onGet(`${process.env.PUBLIC_URL}/docs/table-of-contents.yaml`).reply(404, expectedData); + await expect(docsService.fetchToc()).rejects.toThrowError(/Request failed with status code 404/); + }); + + it('should fetch doc content', async () => { + const markdown = 'markdown'; + const expectedData = { response: true }; + httpMock.onGet(`${process.env.PUBLIC_URL}/docs/${markdown}`).reply(200, expectedData); + const actualData = await docsService.fetchDocContent(markdown); + expect(actualData).toEqual(expectedData); + }); + + it('should not fetch doc content and throw error', async () => { + const markdown = 'markdown'; + const expectedData = { response: true }; + httpMock.onGet(`${process.env.PUBLIC_URL}/docs/${markdown}`).reply(404, expectedData); + await expect(docsService.fetchDocContent(markdown)).rejects.toThrowError(/Request failed with status code 404/); + }); +}); diff --git a/packages/client/src/services/__tests__/FetchCanaryResultsService.test.ts b/packages/client/src/services/__tests__/FetchCanaryResultsService.test.ts new file mode 100644 index 00000000..e74dc67d --- /dev/null +++ b/packages/client/src/services/__tests__/FetchCanaryResultsService.test.ts @@ -0,0 +1,40 @@ +import MockAdapter from 'axios-mock-adapter'; +import { kayentaClient } from '../KayentaApiService'; +import { fetchCanaryResultsService } from '../index'; + +describe('FetchCanaryResultsService', () => { + let httpMock; + + beforeEach(() => { + httpMock = new MockAdapter(kayentaClient); + }); + + afterEach(() => { + httpMock.restore(); + }); + + it('should poll for response successfully', async () => { + const expectedData = { + complete: true, + config: 'test' + }; + + const url = new RegExp(`/canary/*`); + httpMock.onGet(url).reply(200, expectedData); + const actualData = await fetchCanaryResultsService.pollForResponse(); + expect(actualData).toEqual(expectedData); + }); + + it('should poll for response unsuccessfully', function(done) { + const data = { + complete: false, + config: 'test' + }; + + const url = new RegExp(`/canary/*`); + httpMock.onGet(url).reply(200, data); + const actualData = fetchCanaryResultsService.pollForResponse(); + expect(actualData).resolves.toEqual({}); + done(); + }); +}); diff --git a/packages/client/src/services/__tests__/KayentaApiService.test.ts b/packages/client/src/services/__tests__/KayentaApiService.test.ts new file mode 100644 index 00000000..e5aaf84e --- /dev/null +++ b/packages/client/src/services/__tests__/KayentaApiService.test.ts @@ -0,0 +1,141 @@ +import MockAdapter from 'axios-mock-adapter'; +import KayentaApiService, { kayentaClient } from '../KayentaApiService'; +import { CanaryExecutionResult } from '../../domain/Kayenta'; + +jest.mock('../../util/LoggerFactory', () => { + return { + error: () => {}, + info: () => {} + }; +}); + +describe('KayentaApiService', () => { + let httpMock; + let kayentaApiService; + + beforeEach(() => { + httpMock = new MockAdapter(kayentaClient); + kayentaApiService = new KayentaApiService(); + }); + + afterEach(() => { + httpMock.restore(); + }); + + it('should fetch credentials', async () => { + const expectedData = { response: true }; + httpMock.onGet(`/credentials`).reply(200, expectedData); + const actualData = await kayentaApiService.fetchCredentials(); + expect(actualData).toEqual(expectedData); + }); + + it('should not fetch credentials and throw error', async () => { + jest.spyOn(window, 'alert').mockImplementation(() => {}); + httpMock.onGet(`/credentials`).reply(500); + await expect(kayentaApiService.fetchCredentials()).rejects.toThrowError(/Request failed with status code 500/); + }); + + it('should initiate canary with config with empty data', async () => { + const expectedResponse = { response: true }; + httpMock.onPost(`/canary`).reply(200, expectedResponse); + const actualResponse = await kayentaApiService.initiateCanaryWithConfig(undefined, '', '', ''); + expect(actualResponse).toEqual(expectedResponse); + }); + + it('should initiate canary with config', async () => { + const expectedResponse = { response: true }; + const application = 'application'; + const metricsAccountName = 'metricsAccountName'; + const storageAccountName = 'storageAccountName'; + httpMock.onPost(`/canary`).reply(200, expectedResponse); + const actualResponse = await kayentaApiService.initiateCanaryWithConfig( + {}, + application, + metricsAccountName, + storageAccountName + ); + expect(actualResponse).toEqual(expectedResponse); + }); + + it('should not initiate canary with config and throw error', async () => { + httpMock.onPost(`/canary`).reply(404); + const expectedError = kayentaApiService.initiateCanaryWithConfig(undefined, '', '', ''); + await expect(expectedError).rejects.toThrow(); + }); + + it('should fetch canary execution status response', async () => { + const executionId = 'executionId'; + const expectedData = { response: true }; + httpMock.onGet(`/canary/${executionId}`).reply(200, expectedData); + const actualData = await kayentaApiService.fetchCanaryExecutionStatusResponse(executionId); + expect(actualData).toEqual(expectedData); + }); + + it('should not fetch canary execution status response and throw error', async () => { + const executionId = 'executionId'; + httpMock.onGet(`/canary/${executionId}`).reply(404); + await expect(kayentaApiService.fetchCanaryExecutionStatusResponse(executionId)).rejects.toThrowError( + /Request failed with status code 404/ + ); + }); + + it('should fetch canary config', async () => { + const configId = 'configId'; + const expectedData = { response: true }; + httpMock.onGet(`/canaryConfig/${configId}`).reply(200, expectedData); + const actualData = await kayentaApiService.fetchCanaryConfig(configId); + expect(actualData).toEqual(expectedData); + }); + + it('should not fetch canary config and throw error', async () => { + const configId = 'configId'; + httpMock.onGet(`/canaryConfig/${configId}`).reply(404); + await expect(kayentaApiService.fetchCanaryConfig(configId)).rejects.toThrowError( + /Request failed with status code 404/ + ); + }); + + it('should fetch metric set pair list', async () => { + const metricSetPairListId = 'metricSetPairListId'; + const expectedData = { response: true }; + httpMock.onGet(`/metricSetPairList/${metricSetPairListId}`).reply(200, expectedData); + const actualData = await kayentaApiService.fetchMetricSetPairList(metricSetPairListId); + expect(actualData).toEqual(expectedData); + }); + + it('should not fetch metric set pair list and throw error', async () => { + const metricSetPairListId = 'metricSetPairListId'; + httpMock.onGet(`/metricSetPairList/${metricSetPairListId}`).reply(404); + await expect(kayentaApiService.fetchMetricSetPairList(metricSetPairListId)).rejects.toThrowError( + /Request failed with status code 404/ + ); + }); + + it('should create metric set pair list map with empty results', async () => { + const expectedMetricSetPairListMap = {}; + const actualMetricSetPairListMap = await kayentaApiService.createMetricSetPairListMap([]); + expect(actualMetricSetPairListMap).toEqual(expectedMetricSetPairListMap); + }); + + it('should create metric set pair list map', async () => { + const canaryExecutionResults: CanaryExecutionResult[] = [ + { + executionId: '01EV9V6SXPMCAD038QXDVRHFT1', + executionStatus: 'SUCCEEDED', + result: {}, + judgementStartTimeIso: '2020-02-05T02:00:00Z', + judgementStartTimeMillis: 1580868000000, + judgementEndTimeIso: '2020-02-05T03:00:00Z', + judgementEndTimeMillis: 1580871600000, + warnings: [], + metricSetPairListId: '9b9fe8b0-f470-42dc-bbaa-157abadec9bf' + } + ]; + const metricSetPairListId = '9b9fe8b0-f470-42dc-bbaa-157abadec9bf'; + const expectedData = ['metricSetPairList']; + const expectedMetricSetPairListMap = { '9b9fe8b0-f470-42dc-bbaa-157abadec9bf': ['metricSetPairList'] }; + httpMock.onGet(`/metricSetPairList/${metricSetPairListId}`).reply(200, expectedData); + const actualMetricSetPairListMap = await kayentaApiService.createMetricSetPairListMap(canaryExecutionResults); + expect(actualMetricSetPairListMap).toEqual(expectedMetricSetPairListMap); + }); +}); diff --git a/packages/client/src/services/__tests__/LoadCanaryConfigService.test.ts b/packages/client/src/services/__tests__/LoadCanaryConfigService.test.ts new file mode 100644 index 00000000..d3def1bf --- /dev/null +++ b/packages/client/src/services/__tests__/LoadCanaryConfigService.test.ts @@ -0,0 +1,116 @@ +import MockAdapter from 'axios-mock-adapter'; +import LoadCanaryConfigService from '../LoadCanaryConfigService'; +import axios from 'axios'; + +jest.mock('../../util/LoggerFactory', () => { + return { + error: () => {} + }; +}); + +describe('LoadCanaryConfigService', () => { + let httpMock; + let loadCanaryConfigService; + + beforeEach(() => { + httpMock = new MockAdapter(axios); + loadCanaryConfigService = new LoadCanaryConfigService(); + }); + + afterEach(() => { + httpMock.restore(); + }); + + it('should load canary config from clipboard', async () => { + Object.assign(navigator, { + clipboard: { + readText: () => { + return '{"name": "example-canary-config"}'; + } + } + }); + + const expectedCanaryConfig = { name: 'example-canary-config' }; + jest.spyOn(navigator.clipboard, 'readText'); + jest.spyOn(window, 'alert').mockImplementation(() => {}); + const actualCanaryConfig = await loadCanaryConfigService.loadCanaryFromClipboard(); + expect(actualCanaryConfig).toEqual(expectedCanaryConfig); + }); + + it('should fail loading canary config from clipboard', async () => { + Object.assign(navigator, { + clipboard: { + readText: () => {} + } + }); + + jest.spyOn(navigator.clipboard, 'readText'); + jest.spyOn(window, 'alert').mockImplementation(() => {}); + await loadCanaryConfigService.loadCanaryFromClipboard(); + expect(window.alert).toBeCalled(); + }); + + it('should not error on config editor', async () => { + const hrefExample = 'http://example.com/config/edit'; + Object.defineProperty(window, 'location', { + value: { + href: hrefExample + }, + writable: true + }); + window = Object.create(window); + await expect(loadCanaryConfigService.loadCanaryFromTemplate()).resolves; + }); + + it('should recognize blank config and return true', () => { + const url = 'http://example.com/config/edit'; + const expectedResult = true; + const actualResult = loadCanaryConfigService.isBlankConfig(url); + expect(actualResult).toEqual(expectedResult); + }); + + it('should not recognize blank config and return false', () => { + const url = 'http://example.com/config/edit?tempte=hello-world-config'; + expect(() => { + loadCanaryConfigService.getTemplateName(url); + }).toThrowError(/Template URL does not match pattern/); + }); + + it('should recognize template and return false', () => { + const url = 'http://example.com/template=test'; + const expectedResult = false; + const actualResult = loadCanaryConfigService.isBlankConfig(url); + expect(actualResult).toEqual(expectedResult); + }); + + it('should get template name', () => { + const url = 'http://example.com/config/edit?template=test'; + const expectedTemplateName = 'test'; + const actualTemplateName = loadCanaryConfigService.getTemplateName(url); + expect(actualTemplateName).toEqual(expectedTemplateName); + }); + + it('should not get template name from pattern mismatch', () => { + const url = 'http://example.com/config/edit?test'; + expect(() => { + loadCanaryConfigService.getTemplateName(url); + }).toThrowError(/Template URL does not match pattern/); + }); + + it('should fetch template content', async () => { + const fileName = "hello-world-config"; + const expectedData = { response: true }; + httpMock.onGet(`${process.env.PUBLIC_URL}/templates/${fileName}`).reply(200, expectedData); + const actualData = await loadCanaryConfigService.fetchTemplateContent(fileName); + expect(actualData).toEqual(expectedData); + }); + + it('should not fetch template content because template is not found', async () => { + const fileName = "fileName"; + const expectedData = { response: true }; + httpMock.onGet(`${process.env.PUBLIC_URL}/templates/${fileName}`).reply(404, expectedData); + await expect(loadCanaryConfigService.fetchTemplateContent(fileName)).rejects.toThrowError( + /Request failed with status code 404/ + ); + }); +}); diff --git a/packages/client/src/services/index.ts b/packages/client/src/services/index.ts index 9360bda4..38f44049 100644 --- a/packages/client/src/services/index.ts +++ b/packages/client/src/services/index.ts @@ -1,11 +1,9 @@ import DocsService from './DocsService'; -import TemplatesService from './TemplatesService'; import KayentaApiService from './KayentaApiService'; import LoadCanaryConfigService from './LoadCanaryConfigService'; import FetchCanaryResultsService from './FetchCanaryResultsService'; export const docsService = new DocsService(); -export const templateService = new TemplatesService(); export const loadCanaryConfigService = new LoadCanaryConfigService(); export const kayentaApiService = new KayentaApiService(); export const fetchCanaryResultsService = new FetchCanaryResultsService(); diff --git a/packages/client/src/stores/CanaryExecutorStore.ts b/packages/client/src/stores/CanaryExecutorStore.ts index f892fa1b..73d68195 100644 --- a/packages/client/src/stores/CanaryExecutorStore.ts +++ b/packages/client/src/stores/CanaryExecutorStore.ts @@ -1,7 +1,6 @@ import { observable, action, computed, toJS } from 'mobx'; import { CanaryExecutionRequest, - CanaryExecutionStatusResponse, CanaryScope, KayentaCredential } from '../domain/Kayenta'; diff --git a/packages/client/src/validation/configValidators.ts b/packages/client/src/validation/configValidators.ts index 8f55f965..145791b6 100644 --- a/packages/client/src/validation/configValidators.ts +++ b/packages/client/src/validation/configValidators.ts @@ -152,9 +152,9 @@ export const validateCanaryMetricConfig = ( if (isQueryTypeSimple) { querySchema = metricSourceIntegrations()[type].canaryMetricSetQueryConfigSchema; } else { - if (type == 'prometheus') { + if (type === 'prometheus') { querySchema = customPrometheusQueryScheme; - } else if (type == 'signalfx') { + } else if (type === 'signalfx') { querySchema = customSignalFXQuerySchema; } } diff --git a/packages/server/package.json b/packages/server/package.json index 62ccf8e8..f259a6c9 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "1.0.1", + "version": "1.1.0", "description": "The backend for the Referee UI", "author": "Justin Field ", "homepage": "", diff --git a/yarn.lock b/yarn.lock index 1a83f422..7a6ca3fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3421,6 +3421,14 @@ axe-core@^4.0.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.1.tgz#70a7855888e287f7add66002211a423937063eaf" integrity sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ== +axios-mock-adapter@^1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.19.0.tgz#9d72e321a6c5418e1eff067aa99761a86c5188a4" + integrity sha512-D+0U4LNPr7WroiBDvWilzTMYPYTuZlbo6BI8YHZtj7wYQS8NkARlP9KBt8IWWHTQJ0q/8oZ0ClPBtKCCkx8cQg== + dependencies: + fast-deep-equal "^3.1.3" + is-buffer "^2.0.3" + axios@^0.21.1: version "0.21.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" @@ -6444,7 +6452,7 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -7930,6 +7938,11 @@ is-buffer@^1.0.2, is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-buffer@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-callable@^1.1.4, is-callable@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"