diff --git a/app-config.yaml b/app-config.yaml index 22ca1fcaff..8dacce900c 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -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} diff --git a/packages/app/package.json b/packages/app/package.json index 4181b036e9..ade10e9c14 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -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:^", diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index c2f297a2d7..4cf6779607 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -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 = ( + + + + diff --git a/plugins/github-issues/src/api/gitHubIssuesApi.ts b/plugins/github-issues/src/api/gitHubIssuesApi.ts index 816345940c..a07e4f5790 100644 --- a/plugins/github-issues/src/api/gitHubIssuesApi.ts +++ b/plugins/github-issues/src/api/gitHubIssuesApi.ts @@ -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, itemsPerRepo: number, + { + filterBy, + orderBy = { + field: 'UPDATED_AT', + direction: 'DESC', + }, + }: IssuesByRepoOptions = {}, ): Promise => { const graphql = await getOctokit(); const safeNames: Array = []; @@ -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 { diff --git a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx b/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx index 574c15d3b3..78cdfbd708 100644 --- a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx +++ b/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx @@ -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 ; diff --git a/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts b/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts index 8d1d0c2056..ab9305693d 100644 --- a/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts +++ b/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts @@ -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, itemsPerRepo: number, + options?: IssuesByRepoOptions, ) => { const gitHubIssuesApi = useApi(gitHubIssuesApiRef); @@ -33,6 +35,7 @@ export const useGetIssuesByRepoFromGitHub = ( return await gitHubIssuesApi.fetchIssuesByRepoFromGitHub( repos, itemsPerRepo, + options, ); } diff --git a/plugins/github-issues/src/types.ts b/plugins/github-issues/src/types.ts new file mode 100644 index 0000000000..ee245929b8 --- /dev/null +++ b/plugins/github-issues/src/types.ts @@ -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'; + }; +} diff --git a/query.graphql b/query.graphql new file mode 100644 index 0000000000..98a72fac26 --- /dev/null +++ b/query.graphql @@ -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 + } + +} + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e5f10c942a..1df05d3c22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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:^"