Add GoCD plugin
Signed-off-by: Julio Zynger <julio.zynger@soundcloud.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { GoCdApi } from './gocdApi';
|
||||
import { GoCdApiError, PipelineHistory } from './gocdApi.model';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
|
||||
export class GoCdClientApi implements GoCdApi {
|
||||
constructor(private readonly discoveryApi: DiscoveryApi) {}
|
||||
|
||||
async getPipelineHistory(
|
||||
pipelineName: string,
|
||||
): Promise<PipelineHistory | GoCdApiError> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl('proxy');
|
||||
const pipelineHistoryResponse = await fetch(
|
||||
`${baseUrl}/gocd/pipelines/${pipelineName}/history`,
|
||||
);
|
||||
|
||||
return await pipelineHistoryResponse.json();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 interface GoCdApiError {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface PipelineHistory {
|
||||
_links: Links;
|
||||
pipelines: Pipeline[];
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
next: Next;
|
||||
}
|
||||
|
||||
export interface Next {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface Pipeline {
|
||||
name: string;
|
||||
counter: number;
|
||||
label: string;
|
||||
natural_order?: number;
|
||||
can_run?: boolean;
|
||||
preparing_to_schedule?: boolean;
|
||||
comment: string | null;
|
||||
scheduled_date?: number;
|
||||
build_cause?: BuildCause;
|
||||
stages: Stage[];
|
||||
}
|
||||
|
||||
export interface BuildCause {
|
||||
trigger_message: string;
|
||||
trigger_forced: boolean;
|
||||
approver: string;
|
||||
material_revisions: MaterialRevision[];
|
||||
}
|
||||
|
||||
export interface MaterialRevision {
|
||||
changed: boolean;
|
||||
material: Material;
|
||||
modifications: Modification[];
|
||||
}
|
||||
|
||||
export interface Material {
|
||||
name: string;
|
||||
fingerprint: string;
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface Modification {
|
||||
revision: string;
|
||||
modified_time: number;
|
||||
user_name: string;
|
||||
comment: string | null;
|
||||
email_address: string | null;
|
||||
}
|
||||
|
||||
export interface Stage {
|
||||
result?: string;
|
||||
status: string;
|
||||
rerun_of_counter?: number | null;
|
||||
name: string;
|
||||
counter: string;
|
||||
scheduled: boolean;
|
||||
approval_type?: string | null;
|
||||
approved_by?: string | null;
|
||||
operate_permission?: boolean;
|
||||
can_run?: boolean;
|
||||
jobs: Job[];
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
name: string;
|
||||
scheduled_date?: number;
|
||||
state: string;
|
||||
result: string;
|
||||
}
|
||||
|
||||
export enum GoCdBuildResultStatus {
|
||||
running,
|
||||
successful,
|
||||
warning,
|
||||
aborted,
|
||||
error,
|
||||
pending,
|
||||
}
|
||||
|
||||
export interface GoCdBuildStageResult {
|
||||
status: GoCdBuildResultStatus;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface GoCdBuildResult {
|
||||
id: number;
|
||||
source: string;
|
||||
stages: GoCdBuildStageResult[];
|
||||
buildSlug: string;
|
||||
message: string;
|
||||
pipeline: string;
|
||||
author: string | undefined;
|
||||
commitHash: string;
|
||||
triggerTime: number | undefined;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { GoCdApiError, PipelineHistory } from './gocdApi.model';
|
||||
|
||||
export interface GoCdApi {
|
||||
getPipelineHistory(
|
||||
pipelineName: string,
|
||||
): Promise<PipelineHistory | GoCdApiError>;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import { GoCdBuildsComponent } from './GoCdBuildsComponent';
|
||||
import { GoCdApi } from '../../api/gocdApi';
|
||||
|
||||
let entityValue: {
|
||||
entity: { metadata: { annotations?: { [key: string]: string } } };
|
||||
};
|
||||
|
||||
const mockApiClient: GoCdApi = {
|
||||
getPipelineHistory: jest.fn(async () => ({
|
||||
_links: { next: { href: 'some-href' } },
|
||||
pipelines: [],
|
||||
})),
|
||||
};
|
||||
|
||||
jest.mock('@backstage/plugin-catalog-react', () => ({
|
||||
useEntity: () => {
|
||||
return entityValue;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@backstage/core-plugin-api', () => ({
|
||||
...jest.requireActual('@backstage/core-plugin-api'),
|
||||
useApi: () => mockApiClient,
|
||||
}));
|
||||
|
||||
describe('GoCdArtifactsComponent', () => {
|
||||
entityValue = { entity: { metadata: {} } };
|
||||
|
||||
const renderComponent = () => render(<GoCdBuildsComponent />);
|
||||
|
||||
it('should display an empty state if an app annotation is missing', async () => {
|
||||
const rendered = renderComponent();
|
||||
|
||||
expect(await rendered.findByText('Missing Annotation')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
MissingAnnotationEmptyState,
|
||||
EmptyState,
|
||||
Page,
|
||||
} from '@backstage/core-components';
|
||||
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
||||
import { useAsync } from 'react-use';
|
||||
import { gocdApiRef } from '../../plugin';
|
||||
import { GoCdBuildsTable } from '../GoCdBuildsTable/GoCdBuildsTable';
|
||||
import { GoCdApiError, PipelineHistory } from '../../api/gocdApi.model';
|
||||
import { Item, Select } from '../Select';
|
||||
|
||||
export const GOCD_APP_ANNOTATION = 'gocd.org/pipelines';
|
||||
|
||||
export const isGoCdAvailable = (entity: Entity): boolean =>
|
||||
Boolean(entity.metadata.annotations?.[GOCD_APP_ANNOTATION]);
|
||||
|
||||
export const GoCdBuildsComponent = (): JSX.Element => {
|
||||
const { entity } = useEntity();
|
||||
const config = useApi(configApiRef);
|
||||
const rawPipelines: string[] | undefined = (
|
||||
entity.metadata.annotations?.[GOCD_APP_ANNOTATION] as string | undefined
|
||||
)
|
||||
?.split(',')
|
||||
.map(p => p.trim());
|
||||
const gocdApi = useApi(gocdApiRef);
|
||||
|
||||
const [selectedPipeline, setSelectedPipeline] = useState<string>(
|
||||
rawPipelines ? rawPipelines[0] : '',
|
||||
);
|
||||
|
||||
const {
|
||||
value: pipelineHistory,
|
||||
loading,
|
||||
error,
|
||||
} = useAsync(async (): Promise<PipelineHistory | GoCdApiError> => {
|
||||
return await gocdApi.getPipelineHistory(selectedPipeline);
|
||||
}, [selectedPipeline]);
|
||||
|
||||
const onSelectedPipelineChanged = (pipeline: string) => {
|
||||
setSelectedPipeline(pipeline);
|
||||
};
|
||||
|
||||
const getSelectionItems = (pipelines: string[]): Item[] => {
|
||||
return pipelines.map(p => ({ label: p, value: p }));
|
||||
};
|
||||
|
||||
function isError(
|
||||
apiResult: PipelineHistory | GoCdApiError | undefined,
|
||||
): apiResult is GoCdApiError {
|
||||
return (apiResult as GoCdApiError)?.message !== undefined;
|
||||
}
|
||||
|
||||
if (!rawPipelines) {
|
||||
return <MissingAnnotationEmptyState annotation={GOCD_APP_ANNOTATION} />;
|
||||
}
|
||||
|
||||
if (isError(pipelineHistory)) {
|
||||
return (
|
||||
<EmptyState
|
||||
title="GoCD pipelines"
|
||||
description={`Could not fetch pipelines defined for entity ${entity.metadata.name}. Error: ${pipelineHistory.message}`}
|
||||
missing="content"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!loading && !pipelineHistory) {
|
||||
return (
|
||||
<EmptyState
|
||||
title="GoCD pipelines"
|
||||
description={`We could not find pipelines defined for entity ${entity.metadata.name}.`}
|
||||
missing="data"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Page themeId="tool">
|
||||
<Content noPadding>
|
||||
<ContentHeader title={entity.metadata.name}>
|
||||
<Select
|
||||
value={selectedPipeline}
|
||||
onChange={pipeline => onSelectedPipelineChanged(pipeline)}
|
||||
label="Pipeline"
|
||||
items={getSelectionItems(rawPipelines)}
|
||||
/>
|
||||
</ContentHeader>
|
||||
<GoCdBuildsTable
|
||||
goCdBaseUrl={config.getString('gocd.baseUrl')}
|
||||
pipelineHistory={pipelineHistory}
|
||||
loading={loading}
|
||||
error={error}
|
||||
/>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 {
|
||||
GoCdBuildsComponent,
|
||||
isGoCdAvailable,
|
||||
GOCD_APP_ANNOTATION,
|
||||
} from './GoCdBuildsComponent';
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { Entity, getEntitySourceLocation } from '@backstage/catalog-model';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { Button } from '@material-ui/core';
|
||||
import GitHubIcon from '@material-ui/icons/GitHub';
|
||||
import {
|
||||
GoCdBuildResult,
|
||||
GoCdBuildResultStatus,
|
||||
PipelineHistory,
|
||||
Pipeline,
|
||||
} from '../../api/gocdApi.model';
|
||||
import { DateTime } from 'luxon';
|
||||
import {
|
||||
Table,
|
||||
TableColumn,
|
||||
Link,
|
||||
SubvalueCell,
|
||||
StatusOK,
|
||||
StatusWarning,
|
||||
StatusAborted,
|
||||
StatusError,
|
||||
StatusRunning,
|
||||
StatusPending,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
type GoCdBuildsProps = {
|
||||
goCdBaseUrl: string;
|
||||
pipelineHistory: PipelineHistory | undefined;
|
||||
loading: boolean;
|
||||
error: Error | undefined;
|
||||
};
|
||||
|
||||
const renderTrigger = (build: GoCdBuildResult): React.ReactNode => {
|
||||
const subvalue = (
|
||||
<>
|
||||
{build.pipeline}
|
||||
<br />
|
||||
{build.author}
|
||||
</>
|
||||
);
|
||||
return <SubvalueCell value={build.message} subvalue={subvalue} />;
|
||||
};
|
||||
|
||||
const renderStages = (build: GoCdBuildResult): React.ReactNode => {
|
||||
return build.stages.map(s => {
|
||||
switch (s.status) {
|
||||
case GoCdBuildResultStatus.successful: {
|
||||
return (
|
||||
<>
|
||||
<StatusOK>{s.text}</StatusOK>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
case GoCdBuildResultStatus.error: {
|
||||
return (
|
||||
<>
|
||||
<StatusError>{s.text}</StatusError>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
case GoCdBuildResultStatus.running: {
|
||||
return (
|
||||
<>
|
||||
<StatusRunning>{s.text}</StatusRunning>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
case GoCdBuildResultStatus.aborted: {
|
||||
return (
|
||||
<>
|
||||
<StatusAborted>{s.text}</StatusAborted>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
case GoCdBuildResultStatus.pending: {
|
||||
return (
|
||||
<>
|
||||
<StatusPending>{s.text}</StatusPending>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<>
|
||||
<StatusWarning>{s.text}</StatusWarning>
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const renderSource = (build: GoCdBuildResult): React.ReactNode => {
|
||||
return (
|
||||
<Button
|
||||
href={build.source}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
startIcon={<GitHubIcon />}
|
||||
>
|
||||
{build.commitHash}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const renderId = (
|
||||
goCdBaseUrl: string,
|
||||
build: GoCdBuildResult,
|
||||
): React.ReactNode => {
|
||||
const goCdBuildUrl = `${goCdBaseUrl}/go/pipelines/value_stream_map/${build.buildSlug}`;
|
||||
return (
|
||||
<SubvalueCell
|
||||
value={
|
||||
<Link to={goCdBuildUrl} target="_blank" rel="noreferrer">
|
||||
{build.id}
|
||||
</Link>
|
||||
}
|
||||
subvalue={
|
||||
build.triggerTime &&
|
||||
DateTime.fromMillis(build.triggerTime).toLocaleString(
|
||||
DateTime.DATETIME_MED,
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderError = (error: Error): JSX.Element => {
|
||||
return <Alert severity="error">{error.message}</Alert>;
|
||||
};
|
||||
|
||||
const toStageStatus = (status: string): GoCdBuildResultStatus => {
|
||||
switch (status.toLocaleLowerCase('en-US')) {
|
||||
case 'passed':
|
||||
return GoCdBuildResultStatus.successful;
|
||||
case 'failed':
|
||||
return GoCdBuildResultStatus.error;
|
||||
case 'aborted':
|
||||
return GoCdBuildResultStatus.aborted;
|
||||
case 'building':
|
||||
return GoCdBuildResultStatus.running;
|
||||
case 'pending':
|
||||
return GoCdBuildResultStatus.pending;
|
||||
default:
|
||||
return GoCdBuildResultStatus.aborted;
|
||||
}
|
||||
};
|
||||
|
||||
const toBuildResults = (
|
||||
entity: Entity,
|
||||
builds: Pipeline[],
|
||||
): GoCdBuildResult[] | undefined => {
|
||||
// TODO(julioz): What if not git 'type'?
|
||||
const entitySourceLocation =
|
||||
getEntitySourceLocation(entity).target.split('/tree')[0];
|
||||
return builds.map(build => ({
|
||||
id: build.counter,
|
||||
source: `${entitySourceLocation}/commit/${build.build_cause?.material_revisions[0]?.modifications[0].revision}`,
|
||||
stages: build.stages.map(s => ({
|
||||
status: toStageStatus(s.status),
|
||||
text: s.name,
|
||||
})),
|
||||
buildSlug: `${build.name}/${build.counter}`,
|
||||
message:
|
||||
build.build_cause?.material_revisions[0]?.modifications[0].comment ?? '',
|
||||
pipeline: build.name,
|
||||
author:
|
||||
build.build_cause?.material_revisions[0]?.modifications[0].user_name,
|
||||
commitHash: build.label,
|
||||
triggerTime: build.scheduled_date,
|
||||
}));
|
||||
};
|
||||
|
||||
export const GoCdBuildsTable = (props: GoCdBuildsProps): JSX.Element => {
|
||||
const { pipelineHistory, loading, error } = props;
|
||||
const { entity } = useEntity();
|
||||
const [, setSelectedSearchTerm] = useState<string>('');
|
||||
|
||||
const columns: TableColumn<GoCdBuildResult>[] = [
|
||||
{
|
||||
title: 'ID',
|
||||
field: 'id',
|
||||
highlight: true,
|
||||
render: build => renderId(props.goCdBaseUrl, build),
|
||||
},
|
||||
{
|
||||
title: 'Trigger',
|
||||
customFilterAndSearch: (query, row: GoCdBuildResult) =>
|
||||
`${row.message} ${row.pipeline} ${row.author}`
|
||||
.toLocaleUpperCase('en-US')
|
||||
.includes(query.toLocaleUpperCase('en-US')),
|
||||
field: 'message',
|
||||
highlight: true,
|
||||
render: build => renderTrigger(build),
|
||||
},
|
||||
{
|
||||
title: 'Stages',
|
||||
field: 'status',
|
||||
render: build => renderStages(build),
|
||||
},
|
||||
{
|
||||
title: 'Source',
|
||||
field: 'source',
|
||||
render: build => renderSource(build),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{error && renderError(error)}
|
||||
{!error && (
|
||||
<Table
|
||||
title="GoCD builds"
|
||||
isLoading={loading}
|
||||
options={{
|
||||
search: true,
|
||||
searchAutoFocus: true,
|
||||
debounceInterval: 800,
|
||||
paging: false,
|
||||
showFirstLastPageButtons: false,
|
||||
pageSize: 10,
|
||||
}}
|
||||
columns={columns}
|
||||
data={
|
||||
pipelineHistory
|
||||
? toBuildResults(entity, pipelineHistory.pipelines) || []
|
||||
: []
|
||||
}
|
||||
onSearchChange={(searchTerm: string) =>
|
||||
setSelectedSearchTerm(searchTerm)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 FormControl from '@material-ui/core/FormControl';
|
||||
import InputLabel from '@material-ui/core/InputLabel';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
|
||||
export type Item = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
formControl: {
|
||||
margin: theme.spacing(1),
|
||||
minWidth: 120,
|
||||
},
|
||||
selectEmpty: {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
type SelectComponentProps = {
|
||||
value: string;
|
||||
items: Item[];
|
||||
label: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const renderItems = (items: Item[]) => {
|
||||
return items.map(item => {
|
||||
return (
|
||||
<MenuItem value={item.value} key={item.label}>
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const SelectComponent = ({
|
||||
value,
|
||||
items,
|
||||
label,
|
||||
onChange,
|
||||
}: SelectComponentProps): JSX.Element => {
|
||||
const classes = useStyles();
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
|
||||
const val = event.target.value as string;
|
||||
onChange(val);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
variant="outlined"
|
||||
className={classes.formControl}
|
||||
disabled={items.length === 0}
|
||||
>
|
||||
<InputLabel>{label}</InputLabel>
|
||||
<Select label={label} value={value} onChange={handleChange}>
|
||||
{renderItems(items)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { SelectComponent as Select } from './Select';
|
||||
export type { Item } from './Select';
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { gocdPlugin } from './plugin';
|
||||
import { createComponentExtension } from '@backstage/core-plugin-api';
|
||||
|
||||
export const EntityGoCdContent = gocdPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityGoCdContent',
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/GoCdBuildsComponent').then(
|
||||
m => m.GoCdBuildsComponent,
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 { gocdPlugin } from './plugin';
|
||||
export { EntityGoCdContent } from './extensions';
|
||||
export {
|
||||
GOCD_APP_ANNOTATION,
|
||||
isGoCdAvailable,
|
||||
} from './components/GoCdBuildsComponent';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { gocdPlugin } from './plugin';
|
||||
|
||||
describe('gocd', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(gocdPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 { GoCdClientApi } from './api/gocdApi.client';
|
||||
import { GoCdApi } from './api/gocdApi';
|
||||
import {
|
||||
discoveryApiRef,
|
||||
createApiRef,
|
||||
createApiFactory,
|
||||
createPlugin,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
export const gocdApiRef = createApiRef<GoCdApi>({
|
||||
id: 'plugin.gocd.service',
|
||||
description: 'Used by the GoCD plugin to retrieve information about builds.',
|
||||
});
|
||||
|
||||
export const gocdPlugin = createPlugin({
|
||||
id: 'gocd',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: gocdApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef },
|
||||
factory: ({ discoveryApi }) => {
|
||||
return new GoCdClientApi(discoveryApi);
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
title: 'gocd',
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
Reference in New Issue
Block a user