Skip to content
This repository has been archived by the owner on Jun 25, 2024. It is now read-only.

Commit

Permalink
feat(refactor): add referee services tests (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
melanahammel authored Jan 13, 2021
1 parent b8c90f2 commit 23d5dfe
Show file tree
Hide file tree
Showing 24 changed files with 442 additions and 66 deletions.
3 changes: 2 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>",
"homepage": "https://example.com/dashboard/",
Expand Down Expand Up @@ -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": {
Expand Down
1 change: 0 additions & 1 deletion packages/client/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/components/PageViewTracker.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default class LoadCanaryConfigNavItem extends React.Component<Props> {
source: 'file',
name: safeGet(() => unvalidatedJsonObject.name).orElse('UNKNOWN')
});
alert('Successfully loaded canary config from file');
}

render(): React.ReactNode {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { filterNansFromData, calculateTimeLabels } from './IndividualMetricView';
import { MetricSetPair } from '../../domain/Kayenta';

Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export default class SignalFxMetricModal extends AbstractMetricModal<SignalFxCan
<a
href="https://developers.signalfx.com/signalflow_analytics/signalflow_overview.html#_computation_behavior"
target="_blank"
rel="noreferrer"
>
Using SignalFlow
</a>{' '}
Expand Down
1 change: 0 additions & 1 deletion packages/client/src/metricSources/SignalFx/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { lifetimeMillis, scale, timeLabels } from './index';
import Optional from 'optional-js';

Expand Down
1 change: 0 additions & 1 deletion packages/client/src/metricSources/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { defaultGraphDataMapper } from './index';

it('generates default graph labels with populated parameters', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/metricSources/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
26 changes: 22 additions & 4 deletions packages/client/src/services/DocsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ export default class DocsService {
async fetchAndUpdateToc(): Promise<void> {
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.
Expand All @@ -73,6 +73,15 @@ export default class DocsService {
}
}

async fetchToc(): Promise<string> {
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')
Expand All @@ -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<string> {
try {
const response = await axios.get(`${process.env.PUBLIC_URL}/docs/${markdown}`);
return response.data;
} catch (e) {
throw e;
}
}
}
24 changes: 14 additions & 10 deletions packages/client/src/services/FetchCanaryResultsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
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<CanaryExecutionStatusResponse> {
const data = async () => {
await delay(1000);
response = await kayentaApiService.fetchCanaryExecutionStatusResponse(canaryExecutorStore.canaryExecutionId);
canaryExecutorStore.updateStageStatus(response.stageStatus);
};

do {
await data();
} while (!response.complete);
return response;
}
}
3 changes: 2 additions & 1 deletion packages/client/src/services/KayentaApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
Expand All @@ -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;
}
}
Expand Down
54 changes: 41 additions & 13 deletions packages/client/src/services/LoadCanaryConfigService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -27,22 +27,50 @@ export default class LoadCanaryConfigService {

async loadCanaryFromTemplate(): Promise<void> {
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<CanaryConfig> {
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;
}
}
}
22 changes: 0 additions & 22 deletions packages/client/src/services/TemplatesService.ts

This file was deleted.

41 changes: 41 additions & 0 deletions packages/client/src/services/__tests__/DocsService.test.ts
Original file line number Diff line number Diff line change
@@ -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/);
});
});
Original file line number Diff line number Diff line change
@@ -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();
});
});
Loading

0 comments on commit 23d5dfe

Please sign in to comment.