Remove some layouts

This commit is contained in:
Stefan Ålund
2020-02-04 09:59:13 +01:00
parent f89359ee07
commit 41a260e919
25 changed files with 0 additions and 936 deletions
@@ -1,34 +0,0 @@
import { buildComponentInApp } from 'testUtils';
import ClientSDKLayout from 'shared/components/layout/clientSDK/ClientSDKLayout';
describe('<ClientSDKLayout />', () => {
it('Renders a progress bar while loading', async () => {
const { getByTestId } = buildComponentInApp(ClientSDKLayout)
.withProps({ id: 'test' })
.withApolloLoading()
.render();
expect(getByTestId('progress')).toBeInTheDocument();
});
it('Renders client sdk layout', async () => {
const rendered = buildComponentInApp(ClientSDKLayout)
.withTheme()
.withProps({ id: 'test' })
.withRouterEntries(['/client-sdks/test'])
.withApolloData({
clientSdk: {
id: 'test',
componentType: 'client-sdk',
owner: {
id: 'test-squad',
name: 'test-squad',
type: 'squad',
},
},
})
.render();
rendered.getByText('Client SDK');
rendered.getByText('test-squad');
});
});
@@ -1,59 +0,0 @@
import React from 'react';
import gql from 'graphql-tag';
import { Content, Header, OwnerHeaderLabel, Page } from 'shared/components/layout';
import { theme } from 'core/app/PageThemeProvider';
import QueryLayout, { ComponentLayoutProps } from 'shared/components/layout/QueryLayout';
import ClientSDKNavigation from './ClientSDKNavigation';
export default function ClientSDKLayout({
id,
fragment,
children,
emptyHelperText,
...otherProps
}: ComponentLayoutProps) {
const query = gql`
query($id: String!) {
clientSdk(id: $id) {
id
componentType
owner {
id
name
type
}
${fragment ? `...${fragment.definitions[0].name.value}` : ''}
}
}
${fragment || ''}
`;
const emptyHelperTextWithDefault = emptyHelperText || `We couldn't find a client sdk named '${id}'`;
return (
<QueryLayout query={query} variables={{ id }} emptyHelperText={emptyHelperTextWithDefault} {...otherProps}>
{data => {
const clientSdk = data.clientSdk;
return (
<Page theme={theme.library}>
<Header title={clientSdk.id} type="Client SDK" component={clientSdk}>
<OwnerHeaderLabel owner={clientSdk.owner} />
</Header>
<ClientSDKNavigation id={clientSdk.id} />
<Content>{typeof children === 'function' ? children(clientSdk) : children}</Content>
</Page>
);
}}
</QueryLayout>
);
}
const defaultProps: Partial<ComponentLayoutProps> = {
handleEmptyCheck: data => !data.clientSdk,
emptyTitleText: 'Client SDK not found',
emptyLink: '/explore/client-sdks',
emptyLinkText: 'All Client SDKs',
};
ClientSDKLayout.defaultProps = defaultProps;
@@ -1,18 +0,0 @@
import React, { Component } from 'react';
import { Navigation, NavItem } from 'shared/components/layout';
class ClientSDKNavigation extends Component {
render() {
const { id } = this.props;
const clientSDKUrl = `/client-sdks/${id}`;
return (
<Navigation>
<NavItem title="Overview" href={clientSDKUrl} />
</Navigation>
);
}
}
export default ClientSDKNavigation;
@@ -1,22 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import ClientSDKNavigation from 'shared/components/layout/clientSDK/ClientSDKNavigation';
import { wrapInThemedTestApp } from 'testUtils';
describe('<ClientSDKLayout.test.js />', () => {
it('renders without exploding', () => {
const rendered = render(wrapInThemedTestApp(<ClientSDKNavigation id="test" repository="a/b" />));
expect(rendered.getByText('Overview')).toBeInTheDocument();
});
it('renders nav items', () => {
const rendered = render(wrapInThemedTestApp(<ClientSDKNavigation id="test" repository="a/b" />));
expect(rendered.getByText('Overview')).toBeInTheDocument();
});
it('renders links', () => {
const rendered = render(wrapInThemedTestApp(<ClientSDKNavigation id="test" repository="a/b" />));
const anchor = rendered.container.querySelector('a');
expect(anchor.href).toContain('client-sdks/test');
});
});
@@ -1,34 +0,0 @@
import React, { Component } from 'react';
import { ContentHeader, Documentation, SupportButton } from 'shared/components/layout';
import Link from 'shared/components/Link';
import Button from '@material-ui/core/Button/Button';
import { Typography } from '@material-ui/core';
class DataEndpointContentHeader extends Component {
render() {
return (
<ContentHeader title="Data Endpoints">
<Link to="/create-component">
<Button variant="contained" color="primary">
Create new data endpoints component
</Button>
</Link>
<Link to="/data-endpoints-editor/create">
<Button variant="contained" color="primary">
Create new data endpoint (α)
</Button>
</Link>
<SupportButton slackChannel="#data-support">
<Typography>All data endpoints that you and your squad(s) own</Typography>
<Documentation>
<Link to="https://confluence.spotify.net/display/DI/Data+Infrastructure">Data Infra Overview</Link>
<Link to="https://developer.spotify.net/products/data-endpoints.html">Data Endpoints</Link>
<Link to="https://confluence.spotify.net/display/DI/Batch+Management">Batch Management Documentation</Link>
</Documentation>
</SupportButton>
</ContentHeader>
);
}
}
export default DataEndpointContentHeader;
@@ -1,23 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInThemedTestApp } from 'testUtils';
import DataEndpointContentHeader from './DataEndpointContentHeader';
describe('<DataEndpointContentHeader />', () => {
it('renders without exploding', () => {
const data = {
id: 'my-id',
componentType: 'data-endpoint',
componentInfoLocationUri: 'ghe:/a/b/c.yaml',
owner: {
name: 'my-owner',
type: 'squad',
},
lifecycle: 'my-lifecycle',
};
const rendered = render(wrapInThemedTestApp(<DataEndpointContentHeader DataEndpoint={data} />));
rendered.getByText('Data Endpoints');
rendered.getByText('Create new data endpoints component');
rendered.getByText('Support');
});
});
@@ -1,68 +0,0 @@
import React, { Component } from 'react';
import gql from 'graphql-tag';
import { theme } from 'core/app/PageThemeProvider';
import { Content, Header, Page, QueryLayout } from 'shared/components/layout';
import DataEndpointNavigation from './DataEndpointNavigation';
import HeaderLabel from 'shared/components/layout/HeaderLabel';
import OwnerHeaderLabel from 'shared/components/layout/OwnerHeaderLabel';
import ComponentWorkflowsEndpointsToolbar from 'shared/components/ComponentWorkflowsEndpointsToolbar';
export default class DataEndpointLayout extends Component {
static defaultProps = {
handleEmptyCheck: data => !data.dataEndpoint,
emptyTitleText: 'Data endpoint not found',
emptyLink: '/data',
emptyLinkText: 'Your data endpoints',
};
render() {
const { id, fragment, children, ...otherProps } = this.props;
const query = gql`
query($id: String!) {
dataEndpoint(id: $id) {
id
owner {
id
name
contactEmail
type
slack
}
storageUriPattern
lifecycle
component {
id
owner {
id
name
}
...ComponentWorkflowsEndpointsDropdownInfo
}
${fragment ? `...${fragment.definitions[0].name.value}` : ''}
}
}
${ComponentWorkflowsEndpointsToolbar.fragment}
${fragment || ''}
`;
const emptyHelperTextWithDefault = `We couldn't find a data endpoint named '${id}'`;
return (
<QueryLayout query={query} variables={{ id }} emptyHelperText={emptyHelperTextWithDefault} {...otherProps}>
{data => {
const dataendpoint = data.dataEndpoint;
return (
<Page theme={theme.endpoint}>
<Header type="Data Endpoint" title={dataendpoint.id} component={dataendpoint.component}>
<OwnerHeaderLabel owner={dataendpoint.owner} />
<HeaderLabel label="Lifecycle" value={dataendpoint.lifecycle} />
</Header>
<DataEndpointNavigation id={dataendpoint.id} uri={dataendpoint.storageUriPattern} />
<Content>{typeof children === 'function' ? children(dataendpoint) : children}</Content>
</Page>
);
}}
</QueryLayout>
);
}
}
@@ -1,56 +0,0 @@
import { buildComponentInApp } from 'testUtils';
import DataEndpointLayout from './DataEndpointLayout';
import gql from 'graphql-tag';
const dataEndpoint = {
id: 'my-id',
componentType: 'data-endpoint',
componentInfoLocationUri: 'ghe:/a/b/c.yaml',
storageUriPattern: 'bq://a/b/c',
owner: {
name: 'my-owner',
type: 'squad',
},
lifecycle: 'lifecycle',
};
describe('<DataEndpointLayout />', () => {
it('renders progress while loading', () => {
const rendered = buildComponentInApp(DataEndpointLayout)
.withApolloLoading()
.render({ id: 'my-id', system: 'system' });
rendered.getByTestId('progress');
});
it('renders data', () => {
const rendered = buildComponentInApp(DataEndpointLayout)
.withTheme()
.withApolloData({ dataEndpoint })
.render({ id: 'my-id', system: 'system' });
rendered.getByText('my-id');
rendered.getByText('Overview');
});
it('renders with fragment', () => {
let receivedData = null;
const rendered = buildComponentInApp(DataEndpointLayout)
.withTheme()
.withApolloData({ dataEndpoint })
.render({
id: 'my-id',
system: 'system',
fragment: gql`
fragment MoreData on Component {
id
}
`,
children: data => {
receivedData = data;
},
});
expect(receivedData).toEqual(expect.objectContaining({ id: 'my-id' }));
rendered.getByText('my-id');
rendered.getByText('Overview');
});
});
@@ -1,26 +0,0 @@
import React, { Component } from 'react';
import { Navigation, NavItem } from 'shared/components/layout';
class DataEndpointNavigation extends Component {
render() {
const { id, uri } = this.props;
const dataEndpointBaseUrl = `/data-endpoints/${id}`;
return (
<Navigation>
<NavItem title="Overview" href={dataEndpointBaseUrl} />
<NavItem title="Downstreams" href={`${dataEndpointBaseUrl}/downstreams`} />
<NavItem title="Upstreams" href={`${dataEndpointBaseUrl}/upstreams`} />
<NavItem title="Lineage" href={`${dataEndpointBaseUrl}/lineage`} />
<NavItem title="Counters" href={`${dataEndpointBaseUrl}/counters`} />
<NavItem title="Metrics" href={`${dataEndpointBaseUrl}/metrics`} />
<NavItem title="Validation" href={`${dataEndpointBaseUrl}/validation`} />
<NavItem title="Profiling" href={`${dataEndpointBaseUrl}/profiling`} />
{uri.startsWith('bq://') && <NavItem title="BigQuery Stats" href={`${dataEndpointBaseUrl}/bqstats`} />}
<NavItem title="Test Certified" href={`${dataEndpointBaseUrl}/tc4x`} isBeta />
</Navigation>
);
}
}
export default DataEndpointNavigation;
@@ -1,17 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInThemedTestApp } from 'testUtils';
import DataEndpointNavigation from './DataEndpointNavigation';
describe('<DataEndpointNavigation />', () => {
it('renders without exploding', () => {
const rendered = render(wrapInThemedTestApp(<DataEndpointNavigation id="id" system="system" uri="gs://b/o" />));
rendered.getByText('Overview');
expect(rendered.queryByText('BigQuery Stats')).not.toBeInTheDocument();
});
it('renders bigquery link', () => {
const rendered = render(wrapInThemedTestApp(<DataEndpointNavigation id="id" system="system" uri="bq://a/b/c" />));
rendered.getByText('BigQuery Stats');
});
});
@@ -1,28 +0,0 @@
import React from 'react';
import DataSciIcon from '@material-ui/icons/Timeline';
import { AppIcon, PlatformIcon, ToolsIcon, SdkIcon } from 'shared/icons';
import { Content, Header, Navigation, NavItem, Page } from 'shared/components/layout';
import { theme } from 'core/app/PageThemeProvider';
const iconStyle = { fill: '#727272' };
export default class ExploreLayout extends React.Component {
render() {
const { children } = this.props;
return (
<Page theme={theme.home}>
<Header title="Explore the Spotify ecosystem" subtitle="Discover data, Apps, Platforms and much more" />
<Navigation condensable>
<NavItem title="Platforms" icon={<PlatformIcon style={iconStyle} />} href="/explore" />
<NavItem title="Infrastructure" icon={<ToolsIcon style={iconStyle} />} href="/explore/infra" />
<NavItem title="Client SDKs" icon={<SdkIcon style={iconStyle} />} href="/explore/client-sdks" />
<NavItem title="Apps" icon={<AppIcon style={iconStyle} />} href="/apps" isBeta />
<NavItem title="Tools (legacy)" icon={<ToolsIcon style={iconStyle} />} href="/explore/tools" />
<NavItem title="Data Science Tools" icon={<DataSciIcon style={iconStyle} />} href="/data-science-tools" />
</Navigation>
<Content>{children}</Content>
</Page>
);
}
}
@@ -1,12 +0,0 @@
import { buildComponentInApp } from 'testUtils';
import ExploreLayout from './ExploreLayout';
describe('<ExploreLayout />', () => {
it('renders without exploding', () => {
const rendered = buildComponentInApp(ExploreLayout)
.withTheme()
.render({ children: 'my-child' });
rendered.getByText('Explore the Spotify ecosystem');
rendered.getByText('my-child');
});
});
@@ -1,20 +0,0 @@
import { buildComponentInApp } from 'testUtils';
import ManageLayout from './ManageLayout';
const user = {
components: [],
workflows: [],
dataEndpoints: [],
googleCloudPlatformProjects: [],
};
describe('<ManageLayout />', () => {
it('renders without exploding', () => {
const rendered = buildComponentInApp(ManageLayout)
.withTheme()
.withApolloData({ user })
.render();
rendered.getByText('Overview');
});
});
@@ -1,36 +0,0 @@
import React, { FC } from 'react';
import { theme } from 'core/app/PageThemeProvider';
import { useUser, useProfile } from 'shared/apis/user';
import { Content, Header, Page } from 'shared/components/layout';
import { getTimeBasedGreeting } from 'shared/apis/time/timeUtil';
import HomePageTimer from 'plugins/homePage/components/HomePageTimer';
import HomeDrawer from 'plugins/homePage/components/HomeDrawer';
import { useThankYouMessage } from './hooks';
import ManageNavigation from './ManageNavigation';
const ManageLayout: FC<{}> = ({ children }) => {
const user = useUser();
const profile = useProfile();
const thankYouMessage = useThankYouMessage(user.id);
const greeting = getTimeBasedGreeting();
return (
<Page theme={theme.home}>
<Header
title={profile ? `${greeting.greeting}, ${profile.givenName}` : greeting.greeting}
subtitle={thankYouMessage}
tooltip={greeting.language}
pageTitleOverride="Manage"
type=""
>
<HomePageTimer />
</Header>
<ManageNavigation />
<Content>{children}</Content>
<HomeDrawer />
</Page>
);
};
export default ManageLayout;
@@ -1,20 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInThemedTestApp } from 'testUtils';
import FeatureFlags from 'shared/apis/featureFlags/featureFlags';
import ManageNavigation from './ManageNavigation';
describe('<ManageNavigation />', () => {
it('renders without exploding', () => {
const rendered = render(wrapInThemedTestApp(<ManageNavigation />));
rendered.getByText('Overview');
expect(rendered.queryByText('Streaming Pipelines')).not.toBeInTheDocument();
});
it('renders bigquery link', () => {
const mock = jest.spyOn(FeatureFlags, 'getItem').mockImplementation(flag => flag === 'galileo');
const rendered = render(wrapInThemedTestApp(<ManageNavigation />));
rendered.getByText('Streaming Pipelines');
mock.mockRestore();
});
});
@@ -1,45 +0,0 @@
import React, { FC } from 'react';
import { Navigation, NavItem } from 'shared/components/layout';
import StreamingIcon from 'shared/assets/icons/stream.svg';
import {
AppFeatureIcon,
DataEndpointsIcon,
GcpProjectIcon,
LibraryIcon,
OtherIcon,
OverviewIcon,
ServiceIcon,
StorageIcon,
WebsiteIcon,
WorkflowIcon,
} from 'shared/icons';
import FeatureFlags from 'shared/apis/featureFlags/featureFlags';
const style = { fill: '#727272' };
const ManageNavigation: FC<{}> = () => (
<Navigation condensable>
<NavItem title="Overview" icon={<OverviewIcon style={style} />} href="/manage">
<NavItem title="Cloud Cost" href="/cost-overview" />
</NavItem>
<NavItem title="Services" icon={<ServiceIcon style={style} />} href={'/services-owned'} />
<NavItem title="Websites" icon={<WebsiteIcon style={style} />} href={'/websites-owned'} />
<NavItem title="Libraries" icon={<LibraryIcon style={style} />} href={'/libraries-owned'} isBeta />
{FeatureFlags.getItem('foss') && (
<NavItem title="FOSS Libraries" icon={<LibraryIcon style={style} />} href={'/foss-owned'} isAlpha />
)}
<NavItem title="Workflows" icon={<WorkflowIcon style={style} />} href={'/workflows-owned'} />
<NavItem title="Data Endpoints" icon={<DataEndpointsIcon style={style} />} href={'/data'} />
{FeatureFlags.getItem('galileo') && (
<NavItem title="Streaming Pipelines" icon={StreamingIcon} href={'/streaming-pipelines-owned'} />
)}
<NavItem title="App Features" icon={<AppFeatureIcon style={style} />} href={'/app-features-owned'} />
<NavItem title="GCP Projects" icon={<GcpProjectIcon style={style} />} href={'/projects-owned'} />
<NavItem title="Managed Storage" icon={<StorageIcon style={style} />} href={'/managed-storage-owned'} />
<NavItem title="Other" icon={<OtherIcon style={style} />} href={'/other-owned'} />
</Navigation>
);
export default ManageNavigation;
@@ -1,72 +0,0 @@
import gql from 'graphql-tag';
import { useQuery } from '@apollo/react-hooks';
import { User, QueryUserArgs, Maybe } from 'generated/graphql/backstage';
export const THANK_YOU_MESSAGE_QUERY = gql`
query($username: String!) {
user(username: $username) {
components {
id
}
dataEndpoints {
id
}
workflows {
id
}
googleCloudPlatformProjects {
id
}
}
}
`;
type QueryResponse = {
user?: Maybe<User>;
};
const LOADING_MESSAGE = 'Thanks for ...';
const FALLBACK_MESSAGE = 'Thanks for being awesome';
/**
* Formulates a personal "thank you" message based on a user's ownership of things.
*
* @param username An LDAP username
*/
export const useThankYouMessage = (username: string) => {
const { data, loading } = useQuery<QueryResponse, QueryUserArgs>(THANK_YOU_MESSAGE_QUERY, {
variables: { username },
});
if (loading) {
return LOADING_MESSAGE;
} else if (!data || !data.user) {
return FALLBACK_MESSAGE;
}
const componentCount =
(data.user.components || []).length + (data.user.workflows || []).length + (data.user.dataEndpoints || []).length;
const projectCount = (data.user.googleCloudPlatformProjects || []).length;
if (componentCount == 0 && projectCount == 0) {
return FALLBACK_MESSAGE;
}
let counts = '';
if (componentCount > 0) {
counts += `${componentCount} component`;
if (componentCount > 1) {
counts += 's';
}
counts += projectCount > 0 ? ' and ' : '';
}
if (projectCount > 0) {
counts += `${projectCount} project`;
if (projectCount > 1) {
counts += 's';
}
}
return `Thanks for taking care of ${counts}`;
};
@@ -1,56 +0,0 @@
import React, { Component } from 'react';
import gql from 'graphql-tag';
import { Content, Header, HeaderLabel, Page, QueryLayout } from 'shared/components/layout';
import ProjectNavigation from 'shared/components/layout/project/ProjectNavigation';
import { theme } from 'core/app/PageThemeProvider';
export default class ProjectLayout extends Component {
static defaultProps = {
handleEmptyCheck: data => !data.googleCloudPlatformProject,
emptyTitleText: 'Project not found',
emptyLink: '/projects-owned',
emptyLinkText: 'Your projects',
};
render() {
const { id, fragment, emptyHelperText, children, ...otherProps } = this.props;
const query = gql`
query($id: String!) {
googleCloudPlatformProject(id: $id) {
id
owner {
id
name
}
${fragment ? `...${fragment.definitions[0].name.value}` : ''}
}
}
${fragment || ''}
`;
const emptyHelperTextWithDefault = emptyHelperText || `We couldn't find a project named '${id}'`;
return (
<QueryLayout query={query} variables={{ id }} emptyHelperText={emptyHelperTextWithDefault} {...otherProps}>
{data => {
const project = data.googleCloudPlatformProject;
return (
<Page theme={theme.project}>
<Header title={project.id} type="Project">
<HeaderLabel
label="Owner"
value={project.owner && project.owner.name}
url={project.owner ? `/org/${project.owner.name}` : ''}
/>
</Header>
<ProjectNavigation projectId={project.id} />
<Content>{typeof children === 'function' ? children(project) : children}</Content>
</Page>
);
}}
</QueryLayout>
);
}
}
@@ -1,45 +0,0 @@
import { buildComponentInApp } from 'testUtils';
import ProjectLayout from './ProjectLayout';
import gql from 'graphql-tag';
describe('<ProjectLayout />', () => {
it('renders progress while loading', () => {
const rendered = buildComponentInApp(ProjectLayout)
.withApolloLoading()
.render({ id: 'my-project' });
rendered.getByTestId('progress');
});
it('renders data', () => {
const rendered = buildComponentInApp(ProjectLayout)
.withTheme()
.withApolloData({ googleCloudPlatformProject: { id: 'my-project' } })
.render({ id: 'my-project' });
rendered.getByText('my-project');
rendered.getByText('Overview');
rendered.getByText('Bigtable');
});
it('renders with fragment', () => {
let receivedData = null;
const rendered = buildComponentInApp(ProjectLayout)
.withTheme()
.withApolloData({ googleCloudPlatformProject: { id: 'my-project' } })
.render({
id: 'my-project',
fragment: gql`
fragment MoreData on Component {
id
}
`,
children: data => {
receivedData = data;
},
});
expect(receivedData).toEqual({ id: 'my-project' });
rendered.getByText('Bigtable');
});
});
@@ -1,19 +0,0 @@
import React, { Component } from 'react';
import { Navigation, NavItem } from 'shared/components/layout';
class ProjectNavigation extends Component {
render() {
const { projectId } = this.props;
const projectBaseUrl = `/projects/${projectId}`;
return (
<Navigation>
<NavItem title="Overview" href={projectBaseUrl} />
<NavItem title="Bigtable" href={`${projectBaseUrl}/bigtable`} />
</Navigation>
);
}
}
export default ProjectNavigation;
@@ -1,70 +0,0 @@
import React, { Component } from 'react';
import gql from 'graphql-tag';
import { Content, Header, HeaderLabel, OwnerHeaderLabel, Page, QueryLayout } from 'shared/components/layout';
import WorkflowNavigation from 'shared/components/layout/workflow/WorkflowNavigation';
import { theme } from 'core/app/PageThemeProvider';
import MissingComponent from 'plugins/workflows/components/MissingComponent';
import ComponentWorkflowsEndpointsToolbar from 'shared/components/ComponentWorkflowsEndpointsToolbar';
export default class WorkflowLayout extends Component {
static defaultProps = {
handleEmptyCheck: data => !data.workflow,
emptyTitleText: 'Workflow not found',
emptyLink: '/workflows-owned',
emptyLinkText: 'Your workflows',
};
render() {
const { id, fragment, emptyHelperText, children, ...otherProps } = this.props;
const query = gql`
query($id: String!) {
workflow(id: $id) {
id
component {
id
lifecycle
owner {
id
name
type
}
...ComponentWorkflowsEndpointsDropdownInfo
}
${fragment ? `...${fragment.definitions[0].name.value}` : ''}
}
}
${ComponentWorkflowsEndpointsToolbar.fragment}
${fragment || ''}
`;
const emptyHelperTextWithDefault = emptyHelperText || `We couldn't find a workflow named '${id}'`;
return (
<QueryLayout query={query} variables={{ id }} emptyHelperText={emptyHelperTextWithDefault} {...otherProps}>
{data => {
const workflow = data.workflow;
if (!workflow.component) {
return <MissingComponent workflowId={workflow.id} />;
}
const { component } = workflow;
return (
<Page theme={theme.workflow}>
<Header title={workflow.id} type="Workflow" component={component}>
<HeaderLabel
label="Component"
value={component && workflow.component.id}
url={component ? `/components/${component.id}` : ''}
/>
<OwnerHeaderLabel owner={component && component.owner} />
<HeaderLabel label="Lifecycle" value={component && component.lifecycle} />
</Header>
<WorkflowNavigation workflowId={workflow.id} />
<Content>{typeof children === 'function' ? children(workflow) : children}</Content>
</Page>
);
}}
</QueryLayout>
);
}
}
@@ -1,69 +0,0 @@
import { buildComponentInApp } from 'testUtils';
import WorkflowLayout from './WorkflowLayout';
import gql from 'graphql-tag';
import FeatureFlags from 'shared/apis/featureFlags/featureFlags';
const workflow = {
id: 'my-workflow',
component: {
id: 'my-component',
owner: { name: 'me' },
},
componentType: 'workflow',
componentInfoLocationUri: 'ghe://a/b/c',
};
describe('<WorkflowLayout />', () => {
it('renders progress while loading', () => {
const rendered = buildComponentInApp(WorkflowLayout)
.withApolloLoading()
.render({ id: 'my-workflow' });
rendered.getByTestId('progress');
});
it('renders data', () => {
const mock = jest.spyOn(FeatureFlags, 'getItem').mockImplementation(flag => flag === 'workflow-alerts');
const rendered = buildComponentInApp(WorkflowLayout)
.withTheme()
.withApolloData({ workflow })
.render({ id: 'my-workflow' });
rendered.getByText('my-workflow');
rendered.getByText('Backfills');
rendered.getByText('Dependencies');
rendered.getByText('Alerting');
mock.mockRestore();
});
it('renders with fragment', () => {
let receivedData = null;
const rendered = buildComponentInApp(WorkflowLayout)
.withTheme()
.withApolloData({
workflow: {
...workflow,
factsJson: JSON.stringify({
service_tier: 1,
}),
},
})
.render({
id: 'my-workflow',
fragment: gql`
fragment MoreData on Component {
id
}
`,
children: data => {
receivedData = data;
},
});
expect(receivedData).toEqual(expect.objectContaining({ id: 'my-workflow' }));
rendered.getByText('my-workflow');
rendered.getByText('Backfills');
rendered.getByText('Dependencies');
expect(rendered.queryByText('Alerting')).toBeNull();
});
});
@@ -1,23 +0,0 @@
import React, { Component } from 'react';
import { Navigation, NavItem } from 'shared/components/layout';
import FeatureFlags from 'shared/apis/featureFlags/featureFlags';
class WorkflowNavigation extends Component {
render() {
const { workflowId } = this.props;
const workflowBaseUrl = `/workflows/${workflowId}`;
return (
<Navigation>
<NavItem title="Overview" href={workflowBaseUrl} />
<NavItem title="Instances" href={`${workflowBaseUrl}/instances`} />
<NavItem title="Backfills" href={`${workflowBaseUrl}/backfills`} exact={false} />
<NavItem title="Dependencies" href={`${workflowBaseUrl}/dependencies`} />
{FeatureFlags.getItem('workflow-alerts') && <NavItem title="Alerting" href={`${workflowBaseUrl}/alerts`} />}
</Navigation>
);
}
}
export default WorkflowNavigation;
@@ -1,25 +0,0 @@
import { ValidatorBase } from 'shared/components/form/validators';
import SysmodelClient from 'shared/apis/sysmodel/SysmodelClient';
export default class ValidatorSysmodelComponent extends ValidatorBase {
validate(value /* , element */) {
return new Promise(result => {
if (!/^[a-z][a-z0-9-]{2,253}[a-z0-9]$/i.test(value)) {
return result(
'Must be 4 to 255 lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited.',
);
}
SysmodelClient.getComponent(value)
.then((/* response */) => {
result(`The component '${value}' already exists`);
})
.catch(error => {
if (error.response.status === 404) {
return result(true);
}
result(`Error response (${error.response.status}) from sysmodel preventing validation`);
});
});
}
}
@@ -1,39 +0,0 @@
import ValidatorSysmodelComponent from './ValidatorSysmodelComponent';
import SysmodelClient from 'shared/apis/sysmodel/SysmodelClient';
describe('ValidatorSysmodelComponent', () => {
it('instantiates without error', () => {
expect(() => {
let validator = new ValidatorSysmodelComponent();
expect(validator).toBeDefined();
}).not.toThrow();
});
describe('has a working validate() method', () => {
it('has sane defaults', async () => {
const result = await new ValidatorSysmodelComponent().validate('');
expect(result).toContain('Must be 4 to 255 lowercase');
});
it('Treats 200 as failure because component already exists', async () => {
const sysmodelResponse = { response: { status: 200 } };
jest.spyOn(SysmodelClient, 'getComponent').mockImplementation(() => Promise.resolve(sysmodelResponse));
const result = await new ValidatorSysmodelComponent().validate('uniq-component-name');
expect(result).toContain("'uniq-component-name' already exists");
});
it('Treats 404 as successful', async () => {
const sysmodelResponse = { response: { status: 404 } };
jest.spyOn(SysmodelClient, 'getComponent').mockImplementation(() => Promise.reject(sysmodelResponse));
const result = await new ValidatorSysmodelComponent().validate('uniq-component-name');
expect(result).toBeTruthy();
});
it('Catches 500 errors', async () => {
const sysmodelResponse = { response: { status: 500 } };
jest.spyOn(SysmodelClient, 'getComponent').mockImplementation(() => Promise.reject(sysmodelResponse));
const result = await new ValidatorSysmodelComponent().validate('uniq-component-name');
expect(result).toContain('Error response (500) from sysmodel preventing validation');
});
});
});