Extract cell of status matrix to a component
Signed-off-by: Niklas Granander <ngranander@spotify.com>
This commit is contained in:
@@ -16,28 +16,14 @@
|
||||
import React from 'react';
|
||||
import { OverviewComponent } from './OverviewComponent';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { XcmetricsApi, xcmetricsApiRef } from '../../api';
|
||||
import { xcmetricsApiRef } from '../../api';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import { mockUserId, mockXcmetricsApi } from '../../test-utils';
|
||||
|
||||
describe('OverviewComponent', () => {
|
||||
it('should render', async () => {
|
||||
const mockUserId = 'mockUser';
|
||||
const mockApi: jest.Mocked<XcmetricsApi> = {
|
||||
getBuilds: jest.fn().mockResolvedValue([
|
||||
{
|
||||
userid: mockUserId,
|
||||
warningCount: 1,
|
||||
duration: 123.45,
|
||||
isCi: false,
|
||||
projectName: 'App',
|
||||
buildStatus: 'succeeded',
|
||||
schema: 'AppSchema',
|
||||
},
|
||||
]),
|
||||
};
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={ApiRegistry.with(xcmetricsApiRef, mockApi)}>
|
||||
<ApiProvider apis={ApiRegistry.with(xcmetricsApiRef, mockXcmetricsApi)}>
|
||||
<OverviewComponent />
|
||||
</ApiProvider>,
|
||||
);
|
||||
@@ -47,12 +33,10 @@ describe('OverviewComponent', () => {
|
||||
});
|
||||
|
||||
it('should render an empty state when no builds exist', async () => {
|
||||
const mockApi: jest.Mocked<XcmetricsApi> = {
|
||||
getBuilds: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
mockXcmetricsApi.getBuilds = jest.fn().mockResolvedValue([]);
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={ApiRegistry.with(xcmetricsApiRef, mockApi)}>
|
||||
<ApiProvider apis={ApiRegistry.with(xcmetricsApiRef, mockXcmetricsApi)}>
|
||||
<OverviewComponent />
|
||||
</ApiProvider>,
|
||||
);
|
||||
@@ -61,12 +45,13 @@ describe('OverviewComponent', () => {
|
||||
|
||||
it('should show an error when API not responding', async () => {
|
||||
const errorMessage = 'MockErrorMessage';
|
||||
const mockApi: jest.Mocked<XcmetricsApi> = {
|
||||
getBuilds: jest.fn().mockRejectedValue({ message: errorMessage }),
|
||||
};
|
||||
|
||||
mockXcmetricsApi.getBuilds = jest
|
||||
.fn()
|
||||
.mockRejectedValue({ message: errorMessage });
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={ApiRegistry.with(xcmetricsApiRef, mockApi)}>
|
||||
<ApiProvider apis={ApiRegistry.with(xcmetricsApiRef, mockXcmetricsApi)}>
|
||||
<OverviewComponent />
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { StatusCellComponent } from './StatusCellComponent';
|
||||
import { xcmetricsApiRef } from '../../api';
|
||||
import { mockBuildId, mockStatus, mockXcmetricsApi } from '../../test-utils';
|
||||
import { formatStatus } from '../../utils';
|
||||
|
||||
describe('StatusCellComponent', () => {
|
||||
it('should render', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={ApiRegistry.with(xcmetricsApiRef, mockXcmetricsApi)}>
|
||||
<StatusCellComponent
|
||||
buildStatus={{ id: mockBuildId, buildStatus: mockStatus }}
|
||||
size={10}
|
||||
spacing={10}
|
||||
/>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
userEvent.hover(rendered.getByTestId(mockBuildId));
|
||||
expect(
|
||||
await rendered.findByText(formatStatus(mockStatus)),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,7 @@
|
||||
import { makeStyles, Tooltip } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { BuildStatusResult, xcmetricsApiRef } from '../../api';
|
||||
import { BuildStatus, BuildStatusResult, xcmetricsApiRef } from '../../api';
|
||||
import { cn, formatDuration, formatStatus } from '../../utils';
|
||||
import { useAsync } from 'react-use';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
@@ -36,7 +36,7 @@ const TooltipContent = ({ buildId }: TooltipContentProps) => {
|
||||
if (error) {
|
||||
return <div>{error.message}</div>;
|
||||
} else if (loading || !build) {
|
||||
return <Progress />;
|
||||
return <Progress style={{ width: 100 }} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -60,11 +60,15 @@ const TooltipContent = ({ buildId }: TooltipContentProps) => {
|
||||
};
|
||||
|
||||
interface StatusCellProps {
|
||||
buildStatus: BuildStatusResult; // TODO: Rename this
|
||||
buildStatus?: BuildStatusResult;
|
||||
size: number;
|
||||
spacing: number;
|
||||
}
|
||||
|
||||
type StatusStyle = {
|
||||
[key in BuildStatus]: any;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme, StatusCellProps>(theme => ({
|
||||
root: {
|
||||
width: ({ size }) => size,
|
||||
@@ -76,33 +80,43 @@ const useStyles = makeStyles<BackstageTheme, StatusCellProps>(theme => ({
|
||||
transform: 'scale(1.2)',
|
||||
},
|
||||
},
|
||||
succeeded: {
|
||||
backgroundColor:
|
||||
theme.palette.type === 'light'
|
||||
? theme.palette.success.light
|
||||
: theme.palette.success.main,
|
||||
},
|
||||
failed: {
|
||||
backgroundColor: theme.palette.error[theme.palette.type],
|
||||
},
|
||||
stopped: {
|
||||
backgroundColor: theme.palette.warning[theme.palette.type],
|
||||
},
|
||||
...({
|
||||
succeeded: {
|
||||
backgroundColor:
|
||||
theme.palette.type === 'light'
|
||||
? theme.palette.success.light
|
||||
: theme.palette.success.main,
|
||||
},
|
||||
} as StatusStyle), // Make sure that key matches a status
|
||||
...({
|
||||
failed: {
|
||||
backgroundColor: theme.palette.error[theme.palette.type],
|
||||
},
|
||||
} as StatusStyle),
|
||||
...({
|
||||
stopped: {
|
||||
backgroundColor: theme.palette.warning[theme.palette.type],
|
||||
},
|
||||
} as StatusStyle),
|
||||
}));
|
||||
|
||||
export const StatusCellComponent = (props: StatusCellProps) => {
|
||||
const classes = useStyles(props);
|
||||
const { buildStatus: buildStatusItem } = props;
|
||||
const { buildStatus } = props;
|
||||
|
||||
if (!buildStatus) {
|
||||
return <div className={classes.root} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={<TooltipContent buildId={buildStatusItem.id} />}
|
||||
title={<TooltipContent buildId={buildStatus.id} />}
|
||||
enterNextDelay={500}
|
||||
arrow
|
||||
>
|
||||
<div
|
||||
data-testid={buildStatusItem.id}
|
||||
className={cn(classes.root, classes[buildStatusItem.buildStatus])}
|
||||
data-testid={buildStatus.id}
|
||||
className={cn(classes.root, classes[buildStatus.buildStatus])}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
+4
-25
@@ -16,40 +16,19 @@
|
||||
import React from 'react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { StatusMatrixComponent } from './StatusMatrixComponent';
|
||||
import { XcmetricsApi, xcmetricsApiRef } from '../../api';
|
||||
import { formatStatus } from '../../utils';
|
||||
import { xcmetricsApiRef } from '../../api';
|
||||
import { mockBuildId, mockXcmetricsApi } from '../../test-utils';
|
||||
|
||||
describe('StatusMatrixComponent', () => {
|
||||
it('should render', async () => {
|
||||
const mockId = 'mockId';
|
||||
const mockStatus = 'succeeded';
|
||||
const mockApi: jest.Mocked<XcmetricsApi> = {
|
||||
getBuildStatuses: jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ id: mockId, buildStatus: mockStatus }]),
|
||||
getBuild: jest.fn().mockResolvedValue({
|
||||
id: mockId,
|
||||
buildStatus: mockStatus,
|
||||
duration: 10.0,
|
||||
startTimestamp: new Date().getTime().toString(),
|
||||
}),
|
||||
getBuilds: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={ApiRegistry.with(xcmetricsApiRef, mockApi)}>
|
||||
<ApiProvider apis={ApiRegistry.with(xcmetricsApiRef, mockXcmetricsApi)}>
|
||||
<StatusMatrixComponent />
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
const cell = rendered.getByTestId(mockId);
|
||||
const cell = rendered.getByTestId(mockBuildId);
|
||||
expect(cell).toBeInTheDocument();
|
||||
|
||||
userEvent.hover(cell);
|
||||
expect(
|
||||
await rendered.findByText(formatStatus(mockStatus)),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,7 +66,13 @@ export const StatusMatrixComponent = () => {
|
||||
>
|
||||
{loading &&
|
||||
[...new Array(cols * MAX_ROWS)].map((_, index) => {
|
||||
return <div key={index} className={classes.cell} />;
|
||||
return (
|
||||
<StatusCellComponent
|
||||
key={index}
|
||||
size={CELL_SIZE}
|
||||
spacing={CELL_MARGIN}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{builds &&
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './mockXcmetricsApi';
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { XcmetricsApi } from '../api';
|
||||
|
||||
export const mockUserId = 'user_id';
|
||||
export const mockBuildId = 'build_id';
|
||||
export const mockStatus = 'succeeded';
|
||||
|
||||
export const mockXcmetricsApi: jest.Mocked<XcmetricsApi> = {
|
||||
getBuildStatuses: jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ id: mockBuildId, status: mockStatus }]),
|
||||
getBuild: jest.fn().mockResolvedValue({
|
||||
id: mockBuildId,
|
||||
buildStatus: 'succeeded',
|
||||
duration: 10.0,
|
||||
startTimestamp: '1626365026',
|
||||
}),
|
||||
getBuilds: jest.fn().mockResolvedValue([
|
||||
{
|
||||
userid: mockUserId,
|
||||
warningCount: 1,
|
||||
duration: 123.45,
|
||||
isCi: false,
|
||||
projectName: 'App',
|
||||
buildStatus: mockStatus,
|
||||
schema: 'AppSchema',
|
||||
},
|
||||
]),
|
||||
getBuildCounts: jest.fn(),
|
||||
};
|
||||
Reference in New Issue
Block a user