Remove some layouts
This commit is contained in:
@@ -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;
|
||||
-23
@@ -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;
|
||||
-17
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user