Merge branch 'master' of https://github.com/backstage/backstage into marley/7678-pull-request-dashboard
Signed-off-by: Marley Powell <Marley.Powell@exclaimer.com>
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
import {
|
||||
DashboardPullRequest,
|
||||
PullRequest,
|
||||
PullRequestOptions,
|
||||
RepoBuild,
|
||||
RepoBuildOptions,
|
||||
Team,
|
||||
@@ -36,6 +38,12 @@ export interface AzureDevOpsApi {
|
||||
options?: RepoBuildOptions,
|
||||
): Promise<{ items: RepoBuild[] }>;
|
||||
|
||||
getPullRequests(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
options?: PullRequestOptions,
|
||||
): Promise<{ items: PullRequest[] }>;
|
||||
|
||||
getDashboardPullRequests(
|
||||
projectName: string,
|
||||
): Promise<DashboardPullRequest[]>;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
import {
|
||||
DashboardPullRequest,
|
||||
PullRequest,
|
||||
PullRequestOptions,
|
||||
RepoBuild,
|
||||
RepoBuildOptions,
|
||||
Team,
|
||||
@@ -42,9 +44,35 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
|
||||
repoName: string,
|
||||
options?: RepoBuildOptions,
|
||||
): Promise<{ items: RepoBuild[] }> {
|
||||
const items = await this.get<RepoBuild[]>(
|
||||
`repo-builds/${projectName}/${repoName}?top=${options?.top}`,
|
||||
);
|
||||
const queryString = new URLSearchParams();
|
||||
if (options?.top) {
|
||||
queryString.append('top', options.top.toString());
|
||||
}
|
||||
const urlSegment = `repo-builds/${encodeURIComponent(
|
||||
projectName,
|
||||
)}/${encodeURIComponent(repoName)}?${queryString}`;
|
||||
|
||||
const items = await this.get<RepoBuild[]>(urlSegment);
|
||||
return { items };
|
||||
}
|
||||
|
||||
public async getPullRequests(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
options?: PullRequestOptions,
|
||||
): Promise<{ items: PullRequest[] }> {
|
||||
const queryString = new URLSearchParams();
|
||||
if (options?.top) {
|
||||
queryString.append('top', options.top.toString());
|
||||
}
|
||||
if (options?.status) {
|
||||
queryString.append('status', options.status.toString());
|
||||
}
|
||||
const urlSegment = `pull-requests/${encodeURIComponent(
|
||||
projectName,
|
||||
)}/${encodeURIComponent(repoName)}?${queryString}`;
|
||||
|
||||
const items = await this.get<PullRequest[]>(urlSegment);
|
||||
return { items };
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 { SvgIcon, SvgIconProps } from '@material-ui/core';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const AzurePipelinesIcon = (props: SvgIconProps) => (
|
||||
<SvgIcon {...props} viewBox="0 0 512 512">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="32"
|
||||
d="M461.81 53.81a4.4 4.4 0 00-3.3-3.39c-54.38-13.3-180 34.09-248.13 102.17a294.9 294.9 0 00-33.09 39.08c-21-1.9-42-.3-59.88 7.5-50.49 22.2-65.18 80.18-69.28 105.07a9 9 0 009.8 10.4l81.07-8.9a180.29 180.29 0 001.1 18.3 18.15 18.15 0 005.3 11.09l31.39 31.39a18.15 18.15 0 0011.1 5.3 179.91 179.91 0 0018.19 1.1l-8.89 81a9 9 0 0010.39 9.79c24.9-4 83-18.69 105.07-69.17 7.8-17.9 9.4-38.79 7.6-59.69a293.91 293.91 0 0039.19-33.09c68.38-68 115.47-190.86 102.37-247.95zM298.66 213.67a42.7 42.7 0 1160.38 0 42.65 42.65 0 01-60.38 0z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="32"
|
||||
d="M109.64 352a45.06 45.06 0 00-26.35 12.84C65.67 382.52 64 448 64 448s65.52-1.67 83.15-19.31A44.73 44.73 0 00160 402.32"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
@@ -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 { AzurePipelinesIcon } from './AzurePipelinesIcon';
|
||||
+44
-2
@@ -20,9 +20,51 @@ import React from 'react';
|
||||
|
||||
export const AzurePullRequestsIcon = (props: SvgIconProps) => (
|
||||
<SvgIcon {...props} viewBox="0 0 512 512">
|
||||
<circle
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="32"
|
||||
cx="128"
|
||||
cy="416"
|
||||
r="48"
|
||||
/>
|
||||
<path
|
||||
d="M160 95.1c0-44.18-35.82-80-80-80c-44.18 0-80 35.82-80 80C0 128.8 19.77 156.9 48 169.2V342.8C19.77 355.1 0 383.2 0 415.1c0 44.18 35.82 80 80 80c44.18 0 80-35.82 80-80c0-32.79-19.77-60.89-48-73.25V169.2C140.2 156.9 160 128.8 160 95.1zM80 439.1c-13.23 0-24-10.77-24-24s10.77-24 24-24c13.23 0 24 10.77 24 24S93.23 439.1 80 439.1zM80 119.1c-13.23 0-24-10.77-24-24s10.77-24 24-24c13.23 0 24 10.77 24 24S93.23 119.1 80 119.1zM464 342.8v-182.8c0-52.94-43.06-96-96-96H320v-48c0-6.219-3.594-11.84-9.219-14.5c-5.594-2.594-12.25-1.781-17.03 2.219l-96 80C194.1 86.75 192 91.25 192 95.1S194.1 105.2 197.8 108.3l96 80C296.7 190.7 300.3 191.1 304 191.1c2.312 0 4.625-.5 6.781-1.5C316.4 187.8 320 182.2 320 175.1v-48h48c17.66 0 32 14.34 32 32v182.8c-28.23 12.36-48 40.46-48 73.25c0 44.18 35.82 80 80 80c44.18 0 80-35.82 80-80C512 383.2 492.2 355.1 464 342.8zM432 439.1c-13.23 0-24-10.77-24-24s10.77-24 24-24c13.23 0 24 10.77 24 24S445.2 439.1 432 439.1z"
|
||||
fill="currentColor"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="32"
|
||||
d="M128 144v224M288 160l-64-64 64-64"
|
||||
/>
|
||||
<circle
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="32"
|
||||
cx="128"
|
||||
cy="96"
|
||||
r="48"
|
||||
/>
|
||||
<circle
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="32"
|
||||
cx="384"
|
||||
cy="416"
|
||||
r="48"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="32"
|
||||
d="M240 96h84a60 60 0 0160 60v212"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
|
||||
@@ -56,7 +56,7 @@ const generateTestData = (rows = 10): RepoBuild[] => {
|
||||
title: `backstage ci - 1.0.0-preview-${rows - i}`,
|
||||
status,
|
||||
result,
|
||||
queueTime: new Date(Date.now() - i * 60000),
|
||||
queueTime: new Date(Date.now() - i * 60000).toISOString(),
|
||||
source: 'refs/heads/main',
|
||||
link: '',
|
||||
});
|
||||
|
||||
@@ -33,8 +33,10 @@ import {
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
import { AzurePipelinesIcon } from '../AzurePipelinesIcon';
|
||||
import { DateTime } from 'luxon';
|
||||
import React from 'react';
|
||||
import { getDurationFromDates } from '../../utils/getDurationFromDates';
|
||||
|
||||
export const getBuildResultComponent = (result: number | undefined) => {
|
||||
switch (result) {
|
||||
@@ -144,12 +146,27 @@ const columns: TableColumn[] = [
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Duration',
|
||||
field: 'queueTime',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography>
|
||||
{getDurationFromDates(row.startTime, row.finishTime)}
|
||||
</Typography>
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Age',
|
||||
field: 'queueTime',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) =>
|
||||
DateTime.fromJSDate(row.queueTime ?? new Date()).toRelative(),
|
||||
(row.queueTime
|
||||
? DateTime.fromISO(row.queueTime)
|
||||
: DateTime.now()
|
||||
).toRelative(),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -178,7 +195,13 @@ export const BuildTable = ({ items, loading, error }: BuildTableProps) => {
|
||||
pageSize: 5,
|
||||
showEmptyDataSourceMessage: !loading,
|
||||
}}
|
||||
title={`Builds (${items ? items.length : 0})`}
|
||||
title={
|
||||
<Box display="flex" alignItems="center">
|
||||
<AzurePipelinesIcon style={{ fontSize: 30 }} />
|
||||
<Box mr={1} />
|
||||
Azure Pipelines - Builds ({items ? items.length : 0})
|
||||
</Box>
|
||||
}
|
||||
data={items ?? []}
|
||||
/>
|
||||
);
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 { PullRequestTable } from '../PullRequestTable/PullRequestTable';
|
||||
import React from 'react';
|
||||
|
||||
export const EntityPageAzurePullRequests = ({
|
||||
defaultLimit,
|
||||
}: {
|
||||
defaultLimit?: number;
|
||||
}) => {
|
||||
return <PullRequestTable defaultLimit={defaultLimit} />;
|
||||
};
|
||||
@@ -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 { EntityPageAzurePullRequests } from './EntityPageAzurePullRequests';
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 { Button, ButtonGroup } from '@material-ui/core';
|
||||
|
||||
import { PullRequestStatus } from '@backstage/plugin-azure-devops-common';
|
||||
import React from 'react';
|
||||
|
||||
export const PullRequestStatusButtonGroup = ({
|
||||
status,
|
||||
setStatus,
|
||||
}: {
|
||||
status: PullRequestStatus;
|
||||
setStatus: (pullRequestStatus: PullRequestStatus) => void;
|
||||
}) => {
|
||||
return (
|
||||
<ButtonGroup aria-label="outlined button group">
|
||||
<Button
|
||||
color={status === PullRequestStatus.Active ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
setStatus(PullRequestStatus.Active);
|
||||
}}
|
||||
>
|
||||
Active
|
||||
</Button>
|
||||
<Button
|
||||
color={status === PullRequestStatus.Completed ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
setStatus(PullRequestStatus.Completed);
|
||||
}}
|
||||
>
|
||||
Completed
|
||||
</Button>
|
||||
<Button
|
||||
color={status === PullRequestStatus.Abandoned ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
setStatus(PullRequestStatus.Abandoned);
|
||||
}}
|
||||
>
|
||||
Abandoned
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
@@ -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 { PullRequestStatusButtonGroup } from './PullRequestStatusButtonGroup';
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 { Box, Chip } from '@material-ui/core';
|
||||
import {
|
||||
Link,
|
||||
ResponseErrorPanel,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import {
|
||||
PullRequest,
|
||||
PullRequestStatus,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { AzurePullRequestsIcon } from '../AzurePullRequestsIcon';
|
||||
import { DateTime } from 'luxon';
|
||||
import { PullRequestStatusButtonGroup } from '../PullRequestStatusButtonGroup';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { usePullRequests } from '../../hooks/usePullRequests';
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'ID',
|
||||
field: 'pullRequestId',
|
||||
highlight: false,
|
||||
width: 'auto',
|
||||
},
|
||||
{
|
||||
title: 'Title',
|
||||
field: 'title',
|
||||
width: 'auto',
|
||||
render: (row: Partial<PullRequest>) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Link to={row.link ?? ''}>{row.title}</Link>
|
||||
{row.isDraft && (
|
||||
<Box paddingLeft={1}>
|
||||
<Chip
|
||||
label="Draft"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Source',
|
||||
field: 'sourceRefName',
|
||||
width: 'auto',
|
||||
},
|
||||
{
|
||||
title: 'Target',
|
||||
field: 'targetRefName',
|
||||
width: 'auto',
|
||||
},
|
||||
{
|
||||
title: 'Created By',
|
||||
field: 'createdBy',
|
||||
width: 'auto',
|
||||
},
|
||||
{
|
||||
title: 'Created',
|
||||
field: 'creationDate',
|
||||
width: 'auto',
|
||||
render: (row: Partial<PullRequest>) =>
|
||||
(row.creationDate
|
||||
? DateTime.fromISO(row.creationDate)
|
||||
: DateTime.now()
|
||||
).toRelative(),
|
||||
},
|
||||
];
|
||||
|
||||
type PullRequestTableProps = {
|
||||
defaultLimit?: number;
|
||||
};
|
||||
|
||||
export const PullRequestTable = ({ defaultLimit }: PullRequestTableProps) => {
|
||||
const [pullRequestStatusState, setPullRequestStatusState] =
|
||||
useState<PullRequestStatus>(PullRequestStatus.Active);
|
||||
const { entity } = useEntity();
|
||||
|
||||
const { items, loading, error } = usePullRequests(
|
||||
entity,
|
||||
defaultLimit,
|
||||
pullRequestStatusState,
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<ResponseErrorPanel error={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
isLoading={loading}
|
||||
columns={columns}
|
||||
options={{
|
||||
search: true,
|
||||
paging: true,
|
||||
pageSize: 5,
|
||||
showEmptyDataSourceMessage: !loading,
|
||||
}}
|
||||
title={
|
||||
<Box display="flex" alignItems="center">
|
||||
<AzurePullRequestsIcon style={{ fontSize: 30 }} />
|
||||
<Box mr={1} />
|
||||
Azure Repos - Pull Requests ({items ? items.length : 0})
|
||||
<Box position="absolute" right={320} top={20}>
|
||||
<PullRequestStatusButtonGroup
|
||||
status={pullRequestStatusState}
|
||||
setStatus={setPullRequestStatusState}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
data={items ?? []}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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 { PullRequestTable } from './PullRequestTable';
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 {
|
||||
PullRequest,
|
||||
PullRequestOptions,
|
||||
PullRequestStatus,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
|
||||
import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { azureDevOpsApiRef } from '../api';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { useAsync } from 'react-use';
|
||||
import { useProjectRepoFromEntity } from './useProjectRepoFromEntity';
|
||||
|
||||
export function usePullRequests(
|
||||
entity: Entity,
|
||||
defaultLimit?: number,
|
||||
requestedStatus?: PullRequestStatus,
|
||||
): {
|
||||
items?: PullRequest[];
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
} {
|
||||
const top = defaultLimit ?? AZURE_DEVOPS_DEFAULT_TOP;
|
||||
const status = requestedStatus ?? PullRequestStatus.Active;
|
||||
const options: PullRequestOptions = {
|
||||
top,
|
||||
status,
|
||||
};
|
||||
|
||||
const api = useApi(azureDevOpsApiRef);
|
||||
const { project, repo } = useProjectRepoFromEntity(entity);
|
||||
|
||||
const { value, loading, error } = useAsync(() => {
|
||||
return api.getPullRequests(project, repo, options);
|
||||
}, [api, project, repo, top, status]);
|
||||
|
||||
return {
|
||||
items: value?.items,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
export {
|
||||
azureDevOpsPlugin,
|
||||
EntityAzurePipelinesContent,
|
||||
EntityAzurePullRequestsContent,
|
||||
isAzureDevOpsAvailable,
|
||||
AzurePullRequestsPage,
|
||||
} from './plugin';
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import {
|
||||
azurePipelinesEntityContentRouteRef,
|
||||
azurePullRequestDashboardRouteRef,
|
||||
azurePullRequestsEntityContentRouteRef,
|
||||
} from './routes';
|
||||
import {
|
||||
createApiFactory,
|
||||
@@ -44,10 +45,6 @@ export const azureDevOpsPlugin = createPlugin({
|
||||
new AzureDevOpsClient({ discoveryApi, identityApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
azurePullRequestDashboard: azurePullRequestDashboardRouteRef,
|
||||
azurePipelinesEntityContent: azurePipelinesEntityContentRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const AzurePullRequestsPage = azureDevOpsPlugin.provide(
|
||||
@@ -69,3 +66,14 @@ export const EntityAzurePipelinesContent = azureDevOpsPlugin.provide(
|
||||
mountPoint: azurePipelinesEntityContentRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const EntityAzurePullRequestsContent = azureDevOpsPlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'EntityAzurePullRequestsContent',
|
||||
component: () =>
|
||||
import('./components/EntityPageAzurePullRequests').then(
|
||||
m => m.EntityPageAzurePullRequests,
|
||||
),
|
||||
mountPoint: azurePullRequestsEntityContentRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
import { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const azurePullRequestDashboardRouteRef = createRouteRef({
|
||||
id: 'azure-pull-request-dashboard',
|
||||
path: '',
|
||||
title: 'Azure Pull Requests',
|
||||
});
|
||||
|
||||
export const azurePipelinesEntityContentRouteRef = createRouteRef({
|
||||
title: 'Azure Pipelines Entity Content',
|
||||
id: 'azure-pipelines-entity-content',
|
||||
});
|
||||
|
||||
export const azurePullRequestsEntityContentRouteRef = createRouteRef({
|
||||
id: 'azure-pull-requests-entity-content',
|
||||
});
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 { getDurationFromDates } from './getDurationFromDates';
|
||||
|
||||
describe('getDurationFromDates', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers('modern');
|
||||
jest.setSystemTime(new Date('2021-10-15T09:45:25.0000000Z'));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe('getDurationFromDates with valid startTime and valid finishTime', () => {
|
||||
it('should return empty result', () => {
|
||||
const startTime = '2021-10-15T09:30:00.0000000Z';
|
||||
const finishTime = '2021-10-15T11:00:00.0000000Z';
|
||||
|
||||
const result = getDurationFromDates(startTime, finishTime);
|
||||
|
||||
expect(result).toEqual('1h, 30m');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDurationFromDates with valid startTime and undefined finishTime', () => {
|
||||
it('should return empty result', () => {
|
||||
const startTime = '2021-10-15T09:30:00.0000000Z';
|
||||
|
||||
const result = getDurationFromDates(startTime, undefined);
|
||||
|
||||
expect(result).toEqual('15m, 25s');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDurationFromDates with undefined startTime and valid finishTime', () => {
|
||||
it('should return empty result', () => {
|
||||
const finishTime = '2021-10-15T11:00:00.0000000Z';
|
||||
|
||||
const result = getDurationFromDates(undefined, finishTime);
|
||||
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDurationFromDates with undefined startTime and undefined finishTime', () => {
|
||||
it('should return empty result', () => {
|
||||
const result = getDurationFromDates(undefined, undefined);
|
||||
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 { DateTime, Interval } from 'luxon';
|
||||
import humanizeDuration from 'humanize-duration';
|
||||
|
||||
export const getDurationFromDates = (
|
||||
startTime?: string,
|
||||
finishTime?: string,
|
||||
): string => {
|
||||
if (!startTime || (!startTime && !finishTime)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const start = DateTime.fromISO(startTime);
|
||||
const finish = finishTime ? DateTime.fromISO(finishTime) : DateTime.now();
|
||||
|
||||
const formatted = Interval.fromDateTimes(start, finish)
|
||||
.toDuration()
|
||||
.valueOf();
|
||||
|
||||
const shortEnglishHumanizer = humanizeDuration.humanizer({
|
||||
language: 'shortEn',
|
||||
languages: {
|
||||
shortEn: {
|
||||
y: () => 'y',
|
||||
mo: () => 'mo',
|
||||
w: () => 'w',
|
||||
d: () => 'd',
|
||||
h: () => 'h',
|
||||
m: () => 'm',
|
||||
s: () => 's',
|
||||
ms: () => 'ms',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return shortEnglishHumanizer(formatted, {
|
||||
largest: 2,
|
||||
round: true,
|
||||
spacer: '',
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user