add filterBy and orderBy props to the github-issues plugin

Signed-off-by: Paul Cowan <paul.cowan@cutting.scot>
This commit is contained in:
Paul Cowan
2022-10-05 21:09:09 +01:00
parent 35f2f51bde
commit e7b90f996a
9 changed files with 175 additions and 6 deletions
+1 -1
View File
@@ -300,7 +300,7 @@ auth:
development:
clientId: ${AUTH_GITHUB_CLIENT_ID}
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL}
# enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL}
gitlab:
development:
clientId: ${AUTH_GITLAB_CLIENT_ID}
+1
View File
@@ -33,6 +33,7 @@
"@backstage/plugin-gcalendar": "workspace:^",
"@backstage/plugin-gcp-projects": "workspace:^",
"@backstage/plugin-github-actions": "workspace:^",
"@backstage/plugin-github-issues": "workspace:^",
"@backstage/plugin-gocd": "workspace:^",
"@backstage/plugin-graphiql": "workspace:^",
"@backstage/plugin-home": "workspace:^",
@@ -153,6 +153,8 @@ import {
ReportIssue,
} from '@backstage/plugin-techdocs-module-addons-contrib';
import { GitHubIssuesCard } from '@backstage/plugin-github-issues';
const customEntityFilterKind = ['Component', 'API', 'System'];
const EntityLayoutWrapper = (props: { children?: ReactNode }) => {
@@ -342,6 +344,10 @@ const overviewContent = (
<EntityCatalogGraphCard variant="gridItem" height={400} />
</Grid>
<Grid item md={6} xs={12}>
<GitHubIssuesCard />
</Grid>
<EntitySwitch>
<EntitySwitch.Case if={isPagerDutyAvailable}>
<Grid item md={6}>
@@ -22,6 +22,7 @@ import {
} from '@backstage/core-plugin-api';
import { readGitHubIntegrationConfigs } from '@backstage/integration';
import { ForwardedError } from '@backstage/errors';
import { IssuesByRepoOptions, IssuesFilters } from '../types';
/** @internal */
export type Assignee = {
@@ -103,6 +104,13 @@ export const gitHubIssuesApi = (
const fetchIssuesByRepoFromGitHub = async (
repos: Array<string>,
itemsPerRepo: number,
{
filterBy,
orderBy = {
field: 'UPDATED_AT',
direction: 'DESC',
},
}: IssuesByRepoOptions = {},
): Promise<IssuesByRepo> => {
const graphql = await getOctokit();
const safeNames: Array<string> = [];
@@ -126,10 +134,17 @@ export const gitHubIssuesApi = (
};
});
// eslint-disable-next-line no-console
console.log(`
---------------------------------------------------
${createIssueByRepoQuery(repositories, itemsPerRepo, { filterBy })}
---------------------------------------------------
`);
let issuesByRepo: IssuesByRepo = {};
try {
issuesByRepo = await graphql(
createIssueByRepoQuery(repositories, itemsPerRepo),
createIssueByRepoQuery(repositories, itemsPerRepo, { filterBy }),
);
} catch (e) {
if (e.data) {
@@ -151,6 +166,34 @@ export const gitHubIssuesApi = (
return { fetchIssuesByRepoFromGitHub };
};
function formatFilterValue(value: IssuesFilters[keyof IssuesFilters]): string {
if (Array.isArray(value)) {
return `[ ${value.map(formatFilterValue).join(', ')}`;
}
return typeof value === 'string' ? `\"${value}\"` : `${value}`;
}
function createFilterByClause(filterBy?: IssuesFilters): string {
if (typeof filterBy === 'undefined') {
return '';
}
return Object.entries(filterBy)
.flatMap(([field, value]) => {
if (typeof value === 'undefined') {
return [];
}
if (field === 'states') {
return [`${field}: ${value.join(', ')}`];
}
return [`${field}: ${formatFilterValue}`];
})
.join(', ');
}
function createIssueByRepoQuery(
repositories: Array<{
safeName: string;
@@ -158,13 +201,15 @@ function createIssueByRepoQuery(
owner: string;
}>,
itemsPerRepo: number,
{ filterBy, orderBy }: IssuesByRepoOptions,
): string {
const fragment = `
fragment issues on Repository {
issues(
states: OPEN
first: ${itemsPerRepo}
orderBy: { field: UPDATED_AT, direction: DESC }
filterBy: { ${createFilterByClause(filterBy)} }
orderBy: { field: ${orderBy?.field}, direction: ${orderBy?.direction} }
) {
totalCount
edges {
@@ -24,6 +24,7 @@ import { useGetIssuesByRepoFromGitHub } from '../../hooks/useGetIssuesByRepoFrom
import { IssuesList } from './IssuesList';
import { NoRepositoriesInfo } from './NoRepositoriesInfo';
import { IssuesByRepoOptions } from '../../types';
/**
* @public
@@ -31,17 +32,18 @@ import { NoRepositoriesInfo } from './NoRepositoriesInfo';
export type GitHubIssuesProps = {
itemsPerPage?: number;
itemsPerRepo?: number;
queryOptions?: IssuesByRepoOptions;
};
export const GitHubIssues = (props: GitHubIssuesProps) => {
const { itemsPerPage = 10, itemsPerRepo = 40 } = props;
const { itemsPerPage = 10, itemsPerRepo = 40, queryOptions } = props;
const { repositories } = useEntityGitHubRepositories();
const {
isLoading,
gitHubIssuesByRepo: issuesByRepository,
retry,
} = useGetIssuesByRepoFromGitHub(repositories, itemsPerRepo);
} = useGetIssuesByRepoFromGitHub(repositories, itemsPerRepo, queryOptions);
if (!repositories.length) {
return <NoRepositoriesInfo />;
@@ -17,10 +17,12 @@ import { useApi } from '@backstage/core-plugin-api';
import useAsyncRetry from 'react-use/lib/useAsyncRetry';
import { gitHubIssuesApiRef } from '../api';
import { IssuesByRepoOptions } from '../types';
export const useGetIssuesByRepoFromGitHub = (
repos: Array<string>,
itemsPerRepo: number,
options?: IssuesByRepoOptions,
) => {
const gitHubIssuesApi = useApi(gitHubIssuesApiRef);
@@ -33,6 +35,7 @@ export const useGetIssuesByRepoFromGitHub = (
return await gitHubIssuesApi.fetchIssuesByRepoFromGitHub(
repos,
itemsPerRepo,
options,
);
}
+34
View File
@@ -0,0 +1,34 @@
/*
* Copyright 2022 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.
*/
type IssueState = 'OPEN' | 'CLOSED';
/** @internal */
export interface IssuesFilters {
assignee?: string;
createdBy?: string;
labels?: string[];
mentioned?: string;
milestone?: string;
states?: IssueState[];
}
export interface IssuesByRepoOptions {
filterBy?: IssuesFilters;
orderBy?: {
field: 'CREATED_AT' | 'UPDATED_AT' | 'COMMENTS';
direction: 'ASC' | 'DESC';
};
}
+77
View File
@@ -0,0 +1,77 @@
/* http://localhost:7007/api/auth/github /*
{
repository(owner: "backstage", name: "backstage") {
issues(first: 1, filterBy: { labels: ["bug"] }) {
nodes {
title
body
bodyHTML
bodyText
number
labels(first: 100) {
nodes {
color
name
id
}
}
author {
url
avatarUrl
login
}
}
}
}
}
fragment issues on Repository {
issues(
states: OPEN
first: 40
orderBy: { field: UPDATED_AT, direction: DESC }
) {
totalCount
edges {
node {
assignees(first: 10) {
edges {
node {
avatarUrl
login
}
}
}
author {
login
}
repository {
nameWithOwner
}
title
url
participants {
totalCount
}
updatedAt
createdAt
comments(last: 1) {
totalCount
}
}
}
}
}
query {
backstage: repository(name: "backstage", owner: "backstage") {
...issues
}
}
+2 -1
View File
@@ -5604,7 +5604,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/plugin-github-issues@workspace:plugins/github-issues":
"@backstage/plugin-github-issues@workspace:^, @backstage/plugin-github-issues@workspace:plugins/github-issues":
version: 0.0.0-use.local
resolution: "@backstage/plugin-github-issues@workspace:plugins/github-issues"
dependencies:
@@ -22004,6 +22004,7 @@ __metadata:
"@backstage/plugin-gcalendar": "workspace:^"
"@backstage/plugin-gcp-projects": "workspace:^"
"@backstage/plugin-github-actions": "workspace:^"
"@backstage/plugin-github-issues": "workspace:^"
"@backstage/plugin-gocd": "workspace:^"
"@backstage/plugin-graphiql": "workspace:^"
"@backstage/plugin-home": "workspace:^"