Service Catalog improvements (#976)
* Service Catalog improvements * Example table data * Add Create button * ContentHeader margin * Fix test * Checking components value * Wrap tests in wrapInThemedTestApp * tests use toBeInTheDocument
This commit is contained in:
@@ -23,6 +23,7 @@ export { default as Content } from './layout/Content/Content';
|
||||
export { default as ContentHeader } from './layout/ContentHeader/ContentHeader';
|
||||
export { default as Header } from './layout/Header/Header';
|
||||
export { default as HeaderLabel } from './layout/HeaderLabel';
|
||||
export { default as HomepageTimer } from './layout/HomepageTimer';
|
||||
export { default as InfoCard } from './layout/InfoCard';
|
||||
export { CardTab, TabbedCard } from './layout/TabbedCard';
|
||||
export { default as ErrorBoundary } from './layout/ErrorBoundary';
|
||||
|
||||
@@ -30,6 +30,7 @@ const useStyles = makeStyles(theme => ({
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
leftItemsBox: {
|
||||
flex: '1 1 auto',
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { HeaderLabel } from '@backstage/core';
|
||||
import HeaderLabel from '../HeaderLabel';
|
||||
|
||||
const timeFormat = { hour: '2-digit', minute: '2-digit' };
|
||||
const utcOptions = { timeZone: 'UTC', ...timeFormat };
|
||||
@@ -34,6 +34,8 @@ describe('CatalogPage', () => {
|
||||
<CatalogPage componentFactory={testComponentFactory} />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
expect(await rendered.findByText('Your components')).toBeInTheDocument();
|
||||
expect(
|
||||
await rendered.findByText('Keep track of your software'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,10 +15,19 @@
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { Content, Header, Page, pageTheme } from '@backstage/core';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
Header,
|
||||
HomepageTimer,
|
||||
SupportButton,
|
||||
Page,
|
||||
pageTheme,
|
||||
} from '@backstage/core';
|
||||
import { useAsync } from 'react-use';
|
||||
import { ComponentFactory } from '../../data/component';
|
||||
import CatalogTable from '../CatalogTable/CatalogTable';
|
||||
import { Button } from '@material-ui/core';
|
||||
|
||||
type CatalogPageProps = {
|
||||
componentFactory: ComponentFactory;
|
||||
@@ -27,8 +36,16 @@ const CatalogPage: FC<CatalogPageProps> = ({ componentFactory }) => {
|
||||
const { value, error, loading } = useAsync(componentFactory.getAllComponents);
|
||||
return (
|
||||
<Page theme={pageTheme.home}>
|
||||
<Header title="Catalog" subtitle="Your components" />
|
||||
<Header title="Service Catalog" subtitle="Keep track of your software">
|
||||
<HomepageTimer />
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentHeader title="Services">
|
||||
<Button variant="contained" color="primary" href="/create">
|
||||
Create Service
|
||||
</Button>
|
||||
<SupportButton>All your components</SupportButton>
|
||||
</ContentHeader>
|
||||
<CatalogTable
|
||||
components={value || []}
|
||||
loading={loading}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInThemedTestApp } from '@backstage/test-utils';
|
||||
import CatalogTable from './CatalogTable';
|
||||
import { Component } from '../../data/component';
|
||||
|
||||
@@ -26,31 +27,37 @@ const components: Component[] = [
|
||||
|
||||
describe('CatalogTable component', () => {
|
||||
it('should render loading when loading prop it set to true', async () => {
|
||||
const rendered = render(<CatalogTable components={[]} loading />);
|
||||
const rendered = render(
|
||||
wrapInThemedTestApp(<CatalogTable components={[]} loading />),
|
||||
);
|
||||
const progress = await rendered.findByTestId('progress');
|
||||
expect(progress).toBeInTheDOM();
|
||||
expect(progress).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render error message when error is passed in props', async () => {
|
||||
const rendered = render(
|
||||
<CatalogTable
|
||||
components={[]}
|
||||
loading={false}
|
||||
error={{ code: 'error' }}
|
||||
/>,
|
||||
wrapInThemedTestApp(
|
||||
<CatalogTable
|
||||
components={[]}
|
||||
loading={false}
|
||||
error={{ code: 'error' }}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
const errorMessage = await rendered.findByText(
|
||||
'Error encountered while fetching components.',
|
||||
);
|
||||
expect(errorMessage).toBeInTheDOM();
|
||||
expect(errorMessage).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display component names when loading has finished and no error occurred', async () => {
|
||||
const rendered = render(
|
||||
<CatalogTable components={components} loading={false} />,
|
||||
wrapInThemedTestApp(
|
||||
<CatalogTable components={components} loading={false} />,
|
||||
),
|
||||
);
|
||||
expect(await rendered.findByText('component1')).toBeInTheDOM();
|
||||
expect(await rendered.findByText('component2')).toBeInTheDOM();
|
||||
expect(await rendered.findByText('component3')).toBeInTheDOM();
|
||||
expect(await rendered.findByText('component1')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('component2')).toBeInTheDocument();
|
||||
expect(await rendered.findByText('component3')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,13 @@
|
||||
*/
|
||||
import React, { FC } from 'react';
|
||||
import { Component } from '../../data/component';
|
||||
import { InfoCard, Progress, Table, TableColumn } from '@backstage/core';
|
||||
import {
|
||||
InfoCard,
|
||||
Progress,
|
||||
Table,
|
||||
TableColumn,
|
||||
StatusOK,
|
||||
} from '@backstage/core';
|
||||
import { Typography, Link } from '@material-ui/core';
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
@@ -27,6 +33,32 @@ const columns: TableColumn[] = [
|
||||
<Link href={`/catalog/${componentData.name}`}>{componentData.name}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'System',
|
||||
field: 'system',
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
field: 'owner',
|
||||
},
|
||||
{
|
||||
title: 'Lifecycle',
|
||||
field: 'lifecycle',
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
field: 'status',
|
||||
render: (componentData: any) => (
|
||||
<>
|
||||
<StatusOK />
|
||||
{componentData.status || 'Up and running'}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
field: 'description',
|
||||
},
|
||||
];
|
||||
|
||||
type CatalogTableProps = {
|
||||
@@ -55,7 +87,7 @@ const CatalogTable: FC<CatalogTableProps> = ({
|
||||
<Table
|
||||
columns={columns}
|
||||
options={{ paging: false }}
|
||||
title="Your Services"
|
||||
title={`Owned (${(components && components.length) || 0})`}
|
||||
data={components}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('ComponentContextMenu', () => {
|
||||
const button = await menu.findByTestId('menu-button');
|
||||
button.click();
|
||||
const unregister = await menu.findByText('Unregister component');
|
||||
expect(unregister).toBeInTheDOM();
|
||||
expect(unregister).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,12 +26,12 @@ describe('ComponentMetadataCard component', () => {
|
||||
const rendered = await render(
|
||||
<ComponentMetadataCard loading={false} component={testComponent} />,
|
||||
);
|
||||
expect(await rendered.findByText('test')).toBeInTheDOM();
|
||||
expect(await rendered.findByText('test')).toBeInTheDocument();
|
||||
});
|
||||
it('should display loader when loading is set to true', async () => {
|
||||
const rendered = await render(
|
||||
<ComponentMetadataCard loading component={undefined} />,
|
||||
);
|
||||
expect(await rendered.findByRole('progressbar')).toBeInTheDOM();
|
||||
expect(await rendered.findByRole('progressbar')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,8 +16,14 @@
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { Typography, Link, Grid } from '@material-ui/core';
|
||||
import HomePageTimer from '../HomepageTimer';
|
||||
import { Content, InfoCard, Header, Page, pageTheme } from '@backstage/core';
|
||||
import {
|
||||
Content,
|
||||
InfoCard,
|
||||
Header,
|
||||
HomepageTimer,
|
||||
Page,
|
||||
pageTheme,
|
||||
} from '@backstage/core';
|
||||
import SquadTechHealth from './SquadTechHealth';
|
||||
import Table from '@material-ui/core/Table';
|
||||
import TableBody from '@material-ui/core/TableBody';
|
||||
@@ -52,7 +58,7 @@ const HomePage: FC<{}> = () => {
|
||||
title={profile ? `Hello, ${profile.givenName}` : 'Hello'}
|
||||
subtitle="Welcome to Backstage"
|
||||
>
|
||||
<HomePageTimer />
|
||||
<HomepageTimer />
|
||||
</Header>
|
||||
<Content>
|
||||
<Grid container direction="row" spacing={3}>
|
||||
@@ -68,7 +74,7 @@ const HomePage: FC<{}> = () => {
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.map((d) => (
|
||||
{data.map(d => (
|
||||
<TableRow key={d.id}>
|
||||
<TableCell>{d.entity}</TableCell>
|
||||
<TableCell>{d.kind}</TableCell>
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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, { FC } from 'react';
|
||||
import { HeaderLabel } from '@backstage/core';
|
||||
|
||||
const timeFormat = { hour: '2-digit', minute: '2-digit' };
|
||||
const utcOptions = { timeZone: 'UTC', ...timeFormat };
|
||||
const nycOptions = { timeZone: 'America/New_York', ...timeFormat };
|
||||
const tyoOptions = { timeZone: 'Asia/Tokyo', ...timeFormat };
|
||||
const stoOptions = { timeZone: 'Europe/Stockholm', ...timeFormat };
|
||||
|
||||
const defaultTimes = {
|
||||
timeNY: '',
|
||||
timeUTC: '',
|
||||
timeTYO: '',
|
||||
timeSTO: '',
|
||||
};
|
||||
|
||||
function getTimes() {
|
||||
const d = new Date();
|
||||
const lang = window.navigator.language;
|
||||
|
||||
// Using the browser native toLocaleTimeString instead of huge moment-tz
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString
|
||||
const timeNY = d.toLocaleTimeString(lang, nycOptions);
|
||||
const timeUTC = d.toLocaleTimeString(lang, utcOptions);
|
||||
const timeTYO = d.toLocaleTimeString(lang, tyoOptions);
|
||||
const timeSTO = d.toLocaleTimeString(lang, stoOptions);
|
||||
|
||||
return { timeNY, timeUTC, timeTYO, timeSTO };
|
||||
}
|
||||
|
||||
const HomePageTimer: FC<{}> = () => {
|
||||
const [{ timeNY, timeUTC, timeTYO, timeSTO }, setTimes] = React.useState(
|
||||
defaultTimes,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimes(getTimes());
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
setTimes(getTimes());
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderLabel label="NYC" value={timeNY} />
|
||||
<HeaderLabel label="UTC" value={timeUTC} />
|
||||
<HeaderLabel label="STO" value={timeSTO} />
|
||||
<HeaderLabel label="TYO" value={timeTYO} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePageTimer;
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { default } from './Timer';
|
||||
@@ -24,11 +24,11 @@ import {
|
||||
ListItemText,
|
||||
Link,
|
||||
} from '@material-ui/core';
|
||||
import Timer from '../Timer';
|
||||
import {
|
||||
Content,
|
||||
InfoCard,
|
||||
Header,
|
||||
HomepageTimer,
|
||||
Page,
|
||||
pageTheme,
|
||||
ContentHeader,
|
||||
@@ -45,7 +45,7 @@ const WelcomePage: FC<{}> = () => {
|
||||
title={`Welcome ${profile.givenName || 'to Backstage'}`}
|
||||
subtitle="Let's start building a better developer experience"
|
||||
>
|
||||
<Timer />
|
||||
<HomepageTimer />
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentHeader title="Getting Started">
|
||||
|
||||
Reference in New Issue
Block a user