Skip to content

Commit

Permalink
refactor(backstage-plugin): Refactor data service
Browse files Browse the repository at this point in the history
Reuse request logic in the data service.
Rename data service from GroupDataService to DoraDataService.
Reorganise tests to describe them better.
Rename test to describe what is being tested.

Addresses #71
  • Loading branch information
kylejwatson committed Nov 29, 2023
1 parent feff7e4 commit bb6f4f4
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 252 deletions.
12 changes: 6 additions & 6 deletions backstage-plugin/plugins/open-dora/dev/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createDevApp } from '@backstage/dev-utils';
import { MockConfigApi } from '@backstage/test-utils';
import React from 'react';
import { openDoraPlugin, OpenDoraPluginPage } from '../src';
import { MockConfigApi } from '@backstage/test-utils';
import {
GroupDataService,
groupDataServiceApiRef,
} from '../src/services/GroupDataService';
DoraDataService,
doraDataServiceApiRef,
} from '../src/services/DoraDataService';

const mockConfig = new MockConfigApi({
'open-dora': {
Expand All @@ -16,9 +16,9 @@ const mockConfig = new MockConfigApi({
createDevApp()
.registerPlugin(openDoraPlugin)
.registerApi({
api: groupDataServiceApiRef,
api: doraDataServiceApiRef,
deps: {},
factory: () => new GroupDataService({ configApi: mockConfig }),
factory: () => new DoraDataService({ configApi: mockConfig }),
})
.addPage({
element: <OpenDoraPluginPage />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { rest } from 'msw';
import React from 'react';
import { baseUrl, metricUrl } from '../../../testing/mswHandlers';
import {
GroupDataService,
groupDataServiceApiRef,
} from '../../services/GroupDataService';
DoraDataService,
doraDataServiceApiRef,
} from '../../services/DoraDataService';
import { server } from '../../setupTests';
import {
DashboardComponent,
Expand All @@ -26,8 +26,8 @@ async function renderComponentWithApis(component: JSX.Element) {
});

const apiRegistry = TestApiRegistry.from([
groupDataServiceApiRef,
new GroupDataService({ configApi: mockConfig }),
doraDataServiceApiRef,
new DoraDataService({ configApi: mockConfig }),
]);

return await renderInTestApp(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { useApi } from '@backstage/core-plugin-api';
import { useEffect, useState } from 'react';
import { dfBenchmarkKey } from '../models/DfBenchmarkData';
import { groupDataServiceApiRef } from '../services/GroupDataService';
import { doraDataServiceApiRef } from '../services/DoraDataService';

export const useMetricBenchmark = (type: string) => {
const groupDataService = useApi(groupDataServiceApiRef);
const doraDataService = useApi(doraDataServiceApiRef);
const [benchmark, setDfBenchmark] = useState<dfBenchmarkKey | undefined>();
const [error, setError] = useState<Error | undefined>();

useEffect(() => {
groupDataService.retrieveBenchmarkData({ type: type }).then(response => {
doraDataService.retrieveBenchmarkData({ type: type }).then(response => {
setDfBenchmark(response.key);
}, setError);
}, [groupDataService, type]);
}, [doraDataService, type]);

return { error: error, benchmark: benchmark };
};
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { useApi } from '@backstage/core-plugin-api';
import { useContext, useEffect, useState } from 'react';
import { MetricData } from '../models/MetricData';
import { groupDataServiceApiRef } from '../services/GroupDataService';
import { doraDataServiceApiRef } from '../services/DoraDataService';
import { MetricContext } from '../services/MetricContext';

export const useMetricData = (type: string) => {
const groupDataService = useApi(groupDataServiceApiRef);
const doraDataService = useApi(doraDataServiceApiRef);
const [chartData, setChartData] = useState<MetricData | undefined>();
const [error, setError] = useState<Error | undefined>();
const { aggregation, team, project } = useContext(MetricContext);

useEffect(() => {
groupDataService
doraDataService
.retrieveMetricDataPoints({
type: type,
team: team,
Expand All @@ -25,7 +25,7 @@ export const useMetricData = (type: string) => {
setError(new Error('No data found'));
}
}, setError);
}, [aggregation, team, project, groupDataService, type]);
}, [aggregation, team, project, doraDataService, type]);

return { error: error, chartData: chartData };
};
10 changes: 5 additions & 5 deletions backstage-plugin/plugins/open-dora/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
} from '@backstage/core-plugin-api';
import { rootRouteRef } from './routes';
import {
GroupDataService,
groupDataServiceApiRef,
} from './services/GroupDataService';
DoraDataService,
doraDataServiceApiRef,
} from './services/DoraDataService';

export const openDoraPlugin = createPlugin({
id: 'opendora',
Expand All @@ -17,9 +17,9 @@ export const openDoraPlugin = createPlugin({
},
apis: [
createApiFactory({
api: groupDataServiceApiRef,
api: doraDataServiceApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) => new GroupDataService({ configApi }),
factory: ({ configApi }) => new DoraDataService({ configApi }),
}),
],
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { MockConfigApi } from '@backstage/test-utils';
import { rest } from 'msw';
import { baseUrl, benchmarkUrl, metricUrl } from '../../testing/mswHandlers';
import { server } from '../setupTests';
import { DoraDataService } from './DoraDataService';

function createService() {
server.use(
rest.get(benchmarkUrl, (req, res, ctx) => {
const params = req.url.searchParams;
const type = params.get('type');

switch (type) {
case 'df': {
return res(
ctx.json({
key: 'lt-6month',
}),
);
}
default: {
return res(ctx.status(400));
}
}
}),
rest.get(metricUrl, (req, res, ctx) => {
const params = req.url.searchParams;
const type = params.get('type');
const aggregation = params.get('aggregation');
const project = params.get('project');
const team = params.get('team');

return res(
ctx.json({
aggregation: aggregation || 'weekly',
dataPoints: [
{
key: `${project}_${team}_${aggregation}_${type}_first_key`,
value: 2.3,
},
],
}),
);
}),
);
const mockConfig = new MockConfigApi({
'open-dora': { apiBaseUrl: baseUrl },
});

return new DoraDataService({ configApi: mockConfig });
}

describe('DoraDataService', () => {
describe('retriveMetricDataPoints', () => {
it('should return data from the server', async () => {
const service = createService();

expect(
await service.retrieveMetricDataPoints({ type: 'df_count' }),
).toEqual({
aggregation: 'weekly',
dataPoints: [{ key: 'null_null_null_df_count_first_key', value: 2.3 }],
});
});

it('should use provided details in the query parameters', async () => {
const service = createService();

expect(
await service.retrieveMetricDataPoints({
type: 'df_count',
aggregation: 'monthly',
project: 'project1',
team: 'team1',
}),
).toEqual({
aggregation: 'monthly',
dataPoints: [
{ key: 'project1_team1_monthly_df_count_first_key', value: 2.3 },
],
});
});

it('should throw an error if the response does not contain metric data', async () => {
const service = createService();

server.use(
rest.get(metricUrl, (_, res, ctx) => {
return res(ctx.json({ other: 'data' }));
}),
);
await expect(
service.retrieveMetricDataPoints({
type: 'df_count',
}),
).rejects.toEqual(new Error('Unexpected response'));
});

it('should throw an error when the server is unreachable', async () => {
const service = createService();

server.use(
rest.get(metricUrl, (_, res) => {
return res.networkError('Host unreachable');
}),
);
await expect(
service.retrieveMetricDataPoints({
type: 'df_count',
}),
).rejects.toEqual(new Error('Failed to fetch'));
});

it('should throw an error when the server returns a non-ok status', async () => {
const service = createService();

server.use(
rest.get(metricUrl, (_, res, ctx) => {
return res(ctx.status(401));
}),
);
await expect(
service.retrieveMetricDataPoints({
type: 'df_count',
}),
).rejects.toEqual(new Error('Unauthorized'));

server.use(
rest.get(metricUrl, (_, res, ctx) => {
return res(ctx.status(500));
}),
);
await expect(
service.retrieveMetricDataPoints({
type: 'df_count',
}),
).rejects.toEqual(new Error('Internal Server Error'));
});
});

describe('retrieveBenchmarkData', () => {
it('should return deployment frequency overall data from the server', async () => {
const service = createService();

expect(await service.retrieveBenchmarkData({ type: 'df' })).toEqual({
key: 'lt-6month',
});
});

it('should throw an error if the response does not contain metric data', async () => {
const service = createService();

server.use(
rest.get(benchmarkUrl, (_, res, ctx) => {
return res(ctx.json({ other: 'data' }));
}),
);
await expect(
service.retrieveBenchmarkData({
type: 'df',
}),
).rejects.toEqual(new Error('Unexpected response'));
});

it('should send the params in the request', async () => {
const service = createService();

await expect(
service.retrieveBenchmarkData({
type: 'invalid_type',
}),
).rejects.toEqual(new Error('Bad Request'));
});

it('should throw an error when the server returns a non-ok status', async () => {
const service = createService();

server.use(
rest.get(benchmarkUrl, (_, res, ctx) => {
return res(ctx.status(401));
}),
);
await expect(
service.retrieveBenchmarkData({
type: 'df',
}),
).rejects.toEqual(new Error('Unauthorized'));

server.use(
rest.get(benchmarkUrl, (_, res, ctx) => {
return res(ctx.status(500));
}),
);
await expect(
service.retrieveBenchmarkData({
type: 'df',
}),
).rejects.toEqual(new Error('Internal Server Error'));
});
});
});
Loading

0 comments on commit bb6f4f4

Please sign in to comment.