From d576e9bb97e9a7d3ad4eb9a6a795c51dda909456 Mon Sep 17 00:00:00 2001 From: Jerry Hogan Date: Tue, 29 Aug 2023 16:13:51 +0100 Subject: [PATCH] Qwik graphql milestone fix (#1955) * chore: fixed types, added milestone filter to issues * chore: filter working now * chore: clean up * fix: remove a ts ignore comment * fix: moved and cleaned some duplicated type definitions --------- Co-authored-by: Mattia Magi --- .../src/components/file-viewer/index.tsx | 2 +- .../src/components/gists/index.tsx | 2 +- .../src/components/issue-pr-card/card.tsx | 10 +- .../src/components/issue-tab-view/data.ts | 14 +- .../src/components/issue-tab-view/index.tsx | 102 ++++----- .../components/issue-tab-view/parseQuery.ts | 60 +----- .../src/components/issue-tab-view/type.ts | 199 +++--------------- .../pull-request-issue-tab.tsx | 65 +++--- .../src/components/repo-filters/types.ts | 5 - .../src/components/repo-pulls/data.ts | 14 +- .../src/components/repo-pulls/index.tsx | 196 ++++++++++------- .../src/components/repo-pulls/parseQuery.ts | 85 +++----- .../src/components/repo-pulls/types.ts | 184 ++++------------ .../src/components/top-repos/index.tsx | 2 +- .../src/components/user-repos/user-repos.tsx | 2 +- .../src/context/issue-pr-store.ts | 7 +- .../src/context/issue-tab-header-dropdown.ts | 1 + .../src/context/pull-request-store.ts | 5 +- .../src/routes/[owner]/[name]/index@named.tsx | 4 +- .../routes/[owner]/[name]/layout-named.tsx | 2 +- .../[owner]/[name]/pulls/index@named.tsx | 1 + .../tree/[branch]/[...path]/index@named.tsx | 2 +- .../src/routes/[user]/index.tsx | 5 +- qwik-graphql-tailwind/src/routes/layout.tsx | 2 +- .../src/routes/orgs/[name]/index.tsx | 5 +- qwik-graphql-tailwind/src/types/index.ts | 31 +++ qwik-graphql-tailwind/src/utils/constants.ts | 75 +++++-- .../src/utils/dynamicColor.ts | 28 +++ .../src/utils/getMilestoneNumber.ts | 4 + qwik-graphql-tailwind/src/utils/helpers.ts | 13 ++ .../src/utils/parseRestAPIPullRequests.ts | 66 ++++++ .../src/utils/queries/issues-query.ts | 8 + .../src/utils/queries/pull-request.ts | 23 ++ qwik-graphql-tailwind/src/utils/types.ts | 14 ++ qwik-graphql-tailwind/src/utils/useQuery.ts | 37 ++-- qwik-graphql-tailwind/vite.config.ts | 2 +- 36 files changed, 605 insertions(+), 672 deletions(-) create mode 100644 qwik-graphql-tailwind/src/types/index.ts create mode 100644 qwik-graphql-tailwind/src/utils/dynamicColor.ts create mode 100644 qwik-graphql-tailwind/src/utils/getMilestoneNumber.ts create mode 100644 qwik-graphql-tailwind/src/utils/parseRestAPIPullRequests.ts diff --git a/qwik-graphql-tailwind/src/components/file-viewer/index.tsx b/qwik-graphql-tailwind/src/components/file-viewer/index.tsx index 629aab343..3d43152f0 100644 --- a/qwik-graphql-tailwind/src/components/file-viewer/index.tsx +++ b/qwik-graphql-tailwind/src/components/file-viewer/index.tsx @@ -75,7 +75,7 @@ export function updateFile(store: FileStore, response: any) { store.isLoading = false; } -export async function fetchFile(payload: FileQueryParams, abortController?: AbortController): Promise { +export async function fetchFile(payload: FileQueryParams, abortController?: AbortController) { const { executeQuery$ } = useQuery(REPO_FILE_QUERY); const resp = await executeQuery$({ diff --git a/qwik-graphql-tailwind/src/components/gists/index.tsx b/qwik-graphql-tailwind/src/components/gists/index.tsx index bf0c51f03..4e1e4404f 100644 --- a/qwik-graphql-tailwind/src/components/gists/index.tsx +++ b/qwik-graphql-tailwind/src/components/gists/index.tsx @@ -57,7 +57,7 @@ export function updateGists(store: GistStore, response: any) { store.isLoading = false; } -export async function fetchGIst(abortController?: AbortController): Promise { +export async function fetchGIst(abortController?: AbortController) { const { executeQuery$ } = useQuery(USER_GISTS_QUERY); const resp = await executeQuery$({ diff --git a/qwik-graphql-tailwind/src/components/issue-pr-card/card.tsx b/qwik-graphql-tailwind/src/components/issue-pr-card/card.tsx index e2bee4deb..1d0842876 100644 --- a/qwik-graphql-tailwind/src/components/issue-pr-card/card.tsx +++ b/qwik-graphql-tailwind/src/components/issue-pr-card/card.tsx @@ -11,7 +11,8 @@ import { MergedPrIcon, ClosedPrIcon, } from '../icons'; -import { Label } from '../repo-pulls/types'; +import { Label } from '~/types'; +import { getTextColor } from '~/utils/dynamicColor'; export interface IssuePrCardProps { data: { @@ -84,8 +85,11 @@ export const IssuePrCard = component$(({ data, type }: IssuePrCardProps) => { data.labels.map((label: Label, i) => ( {label.name} diff --git a/qwik-graphql-tailwind/src/components/issue-tab-view/data.ts b/qwik-graphql-tailwind/src/components/issue-tab-view/data.ts index bc458d85f..7f8094668 100644 --- a/qwik-graphql-tailwind/src/components/issue-tab-view/data.ts +++ b/qwik-graphql-tailwind/src/components/issue-tab-view/data.ts @@ -1,28 +1,28 @@ -import { IssueOrderField, OrderDirection } from './type'; +import { OrderDirection, OrderField } from '~/utils/types'; export const sortOptions = [ { - value: `${IssueOrderField.CreatedAt}^${OrderDirection.Desc}`, + value: `${OrderField.CreatedAt}^${OrderDirection.Desc}`, label: 'Newest', }, { - value: `${IssueOrderField.CreatedAt}^${OrderDirection.Asc}`, + value: `${OrderField.CreatedAt}^${OrderDirection.Asc}`, label: 'Oldest', }, { - value: `${IssueOrderField.Comments}^${OrderDirection.Desc}`, + value: `${OrderField.Comments}^${OrderDirection.Desc}`, label: 'Most Commented', }, { - value: `${IssueOrderField.Comments}^${OrderDirection.Asc}`, + value: `${OrderField.Comments}^${OrderDirection.Asc}`, label: 'Least Commented', }, { label: 'Recently updated', - value: `${IssueOrderField.UpdatedAt}^${OrderDirection.Desc}`, + value: `${OrderField.UpdatedAt}^${OrderDirection.Desc}`, }, { label: 'Least recently updated', - value: `${IssueOrderField.UpdatedAt}^${OrderDirection.Asc}`, + value: `${OrderField.UpdatedAt}^${OrderDirection.Asc}`, }, ]; diff --git a/qwik-graphql-tailwind/src/components/issue-tab-view/index.tsx b/qwik-graphql-tailwind/src/components/issue-tab-view/index.tsx index dbe1b7996..b2d872f5d 100644 --- a/qwik-graphql-tailwind/src/components/issue-tab-view/index.tsx +++ b/qwik-graphql-tailwind/src/components/issue-tab-view/index.tsx @@ -1,19 +1,19 @@ -import { $, component$, useClientEffect$, useContext, useTask$ } from '@builder.io/qwik'; +import { $, component$, useContext, useTask$ } from '@builder.io/qwik'; import { PullRequestIssueTab } from '../pull-request-issue-tab/pull-request-issue-tab'; import { sortOptions } from './data'; import { useQuery } from '../../utils'; import { ISSUES_QUERY } from '../../utils/queries/issues-query'; import { AUTH_TOKEN, DEFAULT_PAGE_SIZE, GITHUB_GRAPHQL } from '../../utils/constants'; import IssuesData from './issues-data'; -import { IssueOrderField, Milestone, OrderDirection, ParsedIssueQuery } from './type'; +import { ParsedIssueQuery } from './type'; import { isBrowser } from '@builder.io/qwik/build'; import { parseQuery } from './parseQuery'; -import { Label } from '../repo-pulls/types'; import { ClearFilterAndSortBtn } from '../clear-filter-and-sort-button'; import { useLocation, useNavigate } from '@builder.io/qwik-city'; import IssuesPRContext, { IssuesPRContextProps } from '../../context/issue-pr-store'; import DropdownContext from '../../context/issue-tab-header-dropdown'; import { Pagination } from '../pagination/pagination'; +import { OrderDirection, OrderField } from '~/utils/types'; export interface IssuesProps { owner: string; @@ -29,7 +29,7 @@ interface IssuesQueryParams { before?: string; orderBy: string; direction: string; - filterBy: { milestone?: string; labels: string[] | undefined }; + filterBy: { milestone?: string; milestoneNumber?: string; labels: string[] | undefined }; } export const IssueTabView = component$(({ owner, name }: IssuesProps) => { @@ -38,9 +38,6 @@ export const IssueTabView = component$(({ owner, name }: IssuesProps) => { const issuesStore = useContext(IssuesPRContext); const dropdownStore = useContext(DropdownContext); - const afterCursor = typeof location.query.after === 'string' ? location.query.after : undefined; - const beforeCursor = typeof location.query.before === 'string' ? location.query.before : undefined; - const hasActiveFilter = dropdownStore.selectedLabel || dropdownStore.selectedSort !== sortOptions[0].value || @@ -50,65 +47,44 @@ export const IssueTabView = component$(({ owner, name }: IssuesProps) => { dropdownStore.selectedLabel = undefined; dropdownStore.selectedMilestones = undefined; dropdownStore.selectedSort = sortOptions[0].value; + dropdownStore.selectedMilestoneNumber = undefined; navigate.path = `${location.pathname}?tab=${issuesStore.activeTab}`; }); - useClientEffect$(async () => { - const abortController = new AbortController(); - issuesStore.loading = true; - const response = await fetchRepoIssues( - { - owner, - name, - after: afterCursor, - before: beforeCursor, - first: afterCursor || !beforeCursor ? DEFAULT_PAGE_SIZE : undefined, - last: beforeCursor ? DEFAULT_PAGE_SIZE : undefined, - orderBy: IssueOrderField.CreatedAt, - direction: OrderDirection.Desc, - filterBy: { - milestone: dropdownStore.selectedMilestones, - labels: dropdownStore.selectedLabel ? [dropdownStore.selectedLabel] : undefined, - }, - }, - abortController - ); - - updateIssueState(issuesStore, parseQuery(response)); - }); - - useTask$(async ({ track }) => { - const abortController = new AbortController(); - issuesStore.loading = true; - const after = track(() => location.query.after); - const before = track(() => location.query.before); - track(() => issuesStore.activeTab); - track(() => dropdownStore.selectedSort); - track(() => dropdownStore.selectedMilestones); - track(() => dropdownStore.selectedLabel); + useTask$( + async ({ track }) => { + const abortController = new AbortController(); + issuesStore.loading = true; + const after = track(() => location.query.after); + const before = track(() => location.query.before); + track(() => dropdownStore.selectedSort); + track(() => dropdownStore.selectedMilestones); + track(() => dropdownStore.selectedLabel); - if (isBrowser) { - const response = await fetchRepoIssues( - { - owner, - name, - after, - before, - first: location.query.after || !location.query.before ? DEFAULT_PAGE_SIZE : undefined, - last: location.query.before ? DEFAULT_PAGE_SIZE : undefined, - orderBy: dropdownStore.selectedSort.split('^')[0], - direction: dropdownStore.selectedSort.split('^')[1], - filterBy: { - milestone: dropdownStore.selectedMilestones, - labels: dropdownStore.selectedLabel ? [dropdownStore.selectedLabel] : undefined, + if (isBrowser) { + const response = await fetchRepoIssues( + { + owner, + name, + after, + before, + first: location.query.after || !location.query.before ? DEFAULT_PAGE_SIZE : undefined, + last: location.query.before ? DEFAULT_PAGE_SIZE : undefined, + orderBy: dropdownStore.selectedSort.split('^')[0] || OrderField.CreatedAt, + direction: dropdownStore.selectedSort.split('^')[1] || OrderDirection.Desc, + filterBy: { + milestoneNumber: dropdownStore.selectedMilestoneNumber, + labels: dropdownStore.selectedLabel ? [dropdownStore.selectedLabel] : undefined, + }, }, - }, - abortController - ); + abortController + ); - updateIssueState(issuesStore, parseQuery(response)); - } - }); + updateIssueState(issuesStore, parseQuery(response)); + } + }, + { eagerness: 'load' } + ); return ( <> @@ -159,8 +135,8 @@ export function updateIssueState(store: IssuesPRContextProps, response: ParsedIs store.openIssues = openIssues.issues; store.closedIssuesCount = closedIssues.totalCount; store.openIssuesCount = openIssues.totalCount; - store.issuesLabel = labels.map((lab: Label) => ({ label: lab.name, value: lab.name })); - store.milestones = milestones.map((milestone: Milestone) => ({ value: milestone.id, label: milestone.title })); + store.issuesLabel = store.issuesLabel.length ? store.issuesLabel : labels; + store.milestones = store.milestones.length ? store.milestones : milestones; store.openPageInfo = openIssues.pageInfo; store.closedPageInfo = closedIssues.pageInfo; store.loading = false; @@ -169,7 +145,7 @@ export function updateIssueState(store: IssuesPRContextProps, response: ParsedIs export async function fetchRepoIssues( { owner, name, first, last, after, before, orderBy, direction, filterBy }: IssuesQueryParams, abortController?: AbortController -): Promise { +) { const { executeQuery$ } = useQuery(ISSUES_QUERY); const resp = await executeQuery$({ signal: abortController?.signal, diff --git a/qwik-graphql-tailwind/src/components/issue-tab-view/parseQuery.ts b/qwik-graphql-tailwind/src/components/issue-tab-view/parseQuery.ts index 59b9a8b29..b04b43758 100644 --- a/qwik-graphql-tailwind/src/components/issue-tab-view/parseQuery.ts +++ b/qwik-graphql-tailwind/src/components/issue-tab-view/parseQuery.ts @@ -1,5 +1,6 @@ -import { Issue, IssuesQuery, Milestone, ParsedIssueQuery } from './type'; -import { Label } from '../repo-pulls/types'; +import { Label } from '~/context/pull-request-store'; +import { Issue, IssuesQuery, ParsedIssueQuery } from './type'; +import { parseMilestones } from '~/utils/helpers'; function parseIssues(connection?: any) { if (!connection) { @@ -20,19 +21,10 @@ function parseIssues(connection?: any) { } const labelNodes = issue.labels?.nodes || []; - const labels = labelNodes.reduce( - (labels: Label[], label: any) => - label - ? [ - ...labels, - { - color: label.color, - name: label.name, - }, - ] - : labels, - [] - ); + const labels = labelNodes.map((label: Label) => ({ + color: label.color, + name: label.name, + })); return [ ...issues, @@ -58,48 +50,18 @@ function parseIssues(connection?: any) { return { issues, totalCount, pageInfo }; } -function parseMilestones(milestones?: any) { - const nodes = milestones?.nodes || []; - return nodes.reduce((milestones: Milestone[], milestone: Milestone) => { - if (!milestone) { - return milestones; - } - - return [ - ...milestones, - { - id: milestone.id, - closed: milestone.closed, - title: milestone.title, - number: milestone.number, - description: milestone.description, - }, - ]; - }, []); -} - export function parseQuery(data: { data: IssuesQuery }): ParsedIssueQuery { const openIssues = parseIssues(data.data.repository?.openIssues); const closedIssues = parseIssues(data.data.repository?.closedIssues); const milestones = parseMilestones(data.data.repository?.milestones); + const labels = data.data.repository?.labels; + + const labelMap = (labels?.nodes || []).map((label: Label) => ({ color: label.color, name: label.name })); - const labelMap = [...closedIssues.issues, ...openIssues.issues].reduce( - (acc: { [key: string]: Label }, issue: Issue) => { - const map: { [key: string]: Label } = {}; - issue.labels.forEach((label: Label) => { - map[label.name] = label; - }); - return { - ...acc, - ...map, - }; - }, - {} - ); return { openIssues, closedIssues, milestones, - labels: Object.values(labelMap) as Label[], + labels: labelMap, }; } diff --git a/qwik-graphql-tailwind/src/components/issue-tab-view/type.ts b/qwik-graphql-tailwind/src/components/issue-tab-view/type.ts index ff7118064..699ac59a7 100644 --- a/qwik-graphql-tailwind/src/components/issue-tab-view/type.ts +++ b/qwik-graphql-tailwind/src/components/issue-tab-view/type.ts @@ -1,157 +1,40 @@ -import { Label } from '../repo-pulls/types'; +import { Label } from '~/context/pull-request-store'; +import { LabelProps, MilestoneProps, PageInfo } from '~/types'; export type IssuesQuery = { - __typename?: 'Query'; - repository?: - | { - __typename?: 'Repository'; - milestones?: - | { - __typename?: 'MilestoneConnection'; - totalCount: number; - nodes?: - | Array< - | { - __typename?: 'Milestone'; - id: string; - closed: boolean; - description?: string | null | undefined; - number: number; - title: string; - } - | null - | undefined - > - | null - | undefined; - pageInfo: { - __typename?: 'PageInfo'; - startCursor?: string | null | undefined; - endCursor?: string | null | undefined; - hasNextPage: boolean; - hasPreviousPage: boolean; - }; - } - | null - | undefined; - closedIssues: { - __typename?: 'IssueConnection'; - totalCount: number; - pageInfo: { - __typename?: 'PageInfo'; - hasNextPage: boolean; - endCursor?: string | null | undefined; - hasPreviousPage: boolean; - startCursor?: string | null | undefined; - }; - nodes?: - | Array< - | { - __typename?: 'Issue'; - id: string; - closed: boolean; - closedAt?: any | null | undefined; - title: string; - number: number; - createdAt: any; - author?: - | { __typename?: 'Bot'; login: string } - | { __typename?: 'EnterpriseUserAccount'; login: string } - | { __typename?: 'Mannequin'; login: string } - | { __typename?: 'Organization'; login: string } - | { __typename?: 'User'; login: string } - | null - | undefined; - comments: { - __typename?: 'IssueCommentConnection'; - totalCount: number; - }; - labels?: - | { - __typename?: 'LabelConnection'; - totalCount: number; - nodes?: - | Array< - | { - __typename?: 'Label'; - color: string; - name: string; - } - | null - | undefined - > - | null - | undefined; - } - | null - | undefined; - } - | null - | undefined - > - | null - | undefined; - }; - openIssues: { - __typename?: 'IssueConnection'; - totalCount: number; - pageInfo: { - __typename?: 'PageInfo'; - hasNextPage: boolean; - endCursor?: string | null | undefined; - hasPreviousPage: boolean; - startCursor?: string | null | undefined; - }; - nodes?: - | Array< - | { - __typename?: 'Issue'; - id: string; - closed: boolean; - title: string; - number: number; - createdAt: any; - author?: - | { __typename?: 'Bot'; login: string } - | { __typename?: 'EnterpriseUserAccount'; login: string } - | { __typename?: 'Mannequin'; login: string } - | { __typename?: 'Organization'; login: string } - | { __typename?: 'User'; login: string } - | null - | undefined; - comments: { - __typename?: 'IssueCommentConnection'; - totalCount: number; - }; - labels?: - | { - __typename?: 'LabelConnection'; - nodes?: - | Array< - | { - __typename?: 'Label'; - color: string; - name: string; - } - | null - | undefined - > - | null - | undefined; - } - | null - | undefined; - } - | null - | undefined - > - | null - | undefined; - }; - } - | null - | undefined; + repository: { + milestones: MilestoneProps; + labels: LabelProps; + openIssues: IssueProps; + closedIssues: IssueProps; + }; }; + +export interface IssueProps { + totalCount: number; + pageInfo: PageInfo; + nodes: IssueNodeProps[]; +} + +export interface IssueNodeProps { + state: string; + createdAt: string; + closedAt: string; + title: string; + author: { + login: string; + }; + url: string; + labels: { + totalCount: number; + nodes: Label[]; + }; + comments: { + totalCount: number; + }; + number: number; +} + export interface Issue { url: string; closedAt: string; @@ -184,17 +67,3 @@ export interface Milestone { number: number; title: string; } - -export enum IssueOrderField { - /** Order issues by comment count */ - Comments = 'COMMENTS', - /** Order issues by creation time */ - CreatedAt = 'CREATED_AT', - /** Order issues by update time */ - UpdatedAt = 'UPDATED_AT', -} - -export enum OrderDirection { - Asc = 'ASC', - Desc = 'DESC', -} diff --git a/qwik-graphql-tailwind/src/components/pull-request-issue-tab/pull-request-issue-tab.tsx b/qwik-graphql-tailwind/src/components/pull-request-issue-tab/pull-request-issue-tab.tsx index c6e7f70cd..152f0b4f5 100644 --- a/qwik-graphql-tailwind/src/components/pull-request-issue-tab/pull-request-issue-tab.tsx +++ b/qwik-graphql-tailwind/src/components/pull-request-issue-tab/pull-request-issue-tab.tsx @@ -4,8 +4,9 @@ import cn from 'classnames'; import { FilterDropdown } from '../filter-dropdown/filter-dropdown'; import IssuesPRContext from '~/context/issue-pr-store'; import DropdownStores from '~/context/issue-tab-header-dropdown'; -import PullRequestContext from '~/context/pull-request-store'; -import { useLocation, useNavigate } from '@builder.io/qwik-city'; +import PullRequestContext, { Label } from '~/context/pull-request-store'; +import { Milestone } from '../issue-tab-view/type'; +import { getSelectedMilestoneNumber } from '~/utils/getMilestoneNumber'; type Dropdowns = { label: string; @@ -18,8 +19,8 @@ export interface PullRequestIssueTabParams { openCount: number; closedCount: number; tabType: 'pr' | 'issue'; - milestonesOption?: Dropdowns[]; - labelOption?: Dropdowns[]; + milestonesOption?: Milestone[]; + labelOption?: Label[]; sortOption: Dropdowns[]; } @@ -32,8 +33,6 @@ export const PullRequestIssueTab = component$( ({ openCount, closedCount, tabType, milestonesOption, labelOption, sortOption }: PullRequestIssueTabParams) => { const tab = tabType === 'issue' ? useContext(IssuesPRContext) : useContext(PullRequestContext); const dropdown = useContext(DropdownStores); - const { pathname } = useLocation(); - const navigate = useNavigate(); const openBtnClasses = cn('text-xs flex items-center gap-1 text-gray-600', { 'font-semibold text-gray-900': tab.activeTab === TABS.OPEN, @@ -48,10 +47,6 @@ export const PullRequestIssueTab = component$( const toggleTab = $((value: TABS) => { tab.activeTab = value; - dropdown.selectedLabel = undefined; - dropdown.selectedSort = dropdown.selectedSort || sortOption[0].value; - dropdown.selectedMilestones = undefined; - navigate.path = `${pathname}?tab=${value}`; }); return ( @@ -72,24 +67,24 @@ export const PullRequestIssueTab = component$( {labelOption && labelOption.length !== 0 && (
- {labelOption.map(({ label, value, color, description }) => ( + {labelOption.map(({ name, color, description }) => (
-
- ) - )} + {milestonesOption.map(({ title, id }) => ( +
+ +
+ ))}
)} diff --git a/qwik-graphql-tailwind/src/components/repo-filters/types.ts b/qwik-graphql-tailwind/src/components/repo-filters/types.ts index a6ccd7eea..dbe2d63a8 100644 --- a/qwik-graphql-tailwind/src/components/repo-filters/types.ts +++ b/qwik-graphql-tailwind/src/components/repo-filters/types.ts @@ -6,11 +6,6 @@ export enum RepositoryOrderField { UpdatedAt = 'UPDATED_AT', } -export enum OrderDirection { - Asc = 'ASC', - Desc = 'DESC', -} - // export type FiltersAPI = ReturnType; export enum TypeFilter { diff --git a/qwik-graphql-tailwind/src/components/repo-pulls/data.ts b/qwik-graphql-tailwind/src/components/repo-pulls/data.ts index 7db40bf45..5b5582d81 100644 --- a/qwik-graphql-tailwind/src/components/repo-pulls/data.ts +++ b/qwik-graphql-tailwind/src/components/repo-pulls/data.ts @@ -1,28 +1,28 @@ -import { OrderDirection, PullRequestOrderField } from './types'; +import { OrderDirection, OrderField } from '~/utils/types'; export const sortOptions = [ { - value: `${PullRequestOrderField.CreatedAt}^${OrderDirection.Desc}`, + value: `${OrderField.CreatedAt}^${OrderDirection.Desc}`, label: 'Newest', }, { - value: `${PullRequestOrderField.CreatedAt}^${OrderDirection.Asc}`, + value: `${OrderField.CreatedAt}^${OrderDirection.Asc}`, label: 'Oldest', }, { label: 'Most commented', - value: `${PullRequestOrderField.Comments}^${OrderDirection.Desc}`, + value: `${OrderField.Comments}^${OrderDirection.Desc}`, }, { label: 'Least commented', - value: `${PullRequestOrderField.Comments}^${OrderDirection.Asc}`, + value: `${OrderField.Comments}^${OrderDirection.Asc}`, }, { label: 'Recently updated', - value: `${PullRequestOrderField.UpdatedAt}^${OrderDirection.Desc}`, + value: `${OrderField.UpdatedAt}^${OrderDirection.Desc}`, }, { label: 'Least reecently updated', - value: `${PullRequestOrderField.UpdatedAt}^${OrderDirection.Asc}`, + value: `${OrderField.UpdatedAt}^${OrderDirection.Asc}`, }, ]; diff --git a/qwik-graphql-tailwind/src/components/repo-pulls/index.tsx b/qwik-graphql-tailwind/src/components/repo-pulls/index.tsx index 7ad9d968f..0779db813 100644 --- a/qwik-graphql-tailwind/src/components/repo-pulls/index.tsx +++ b/qwik-graphql-tailwind/src/components/repo-pulls/index.tsx @@ -1,10 +1,10 @@ import { isBrowser } from '@builder.io/qwik/build'; import { useLocation, useNavigate } from '@builder.io/qwik-city'; -import { $, component$, useClientEffect$, useTask$, useContext } from '@builder.io/qwik'; +import { $, component$, useTask$, useContext } from '@builder.io/qwik'; import { parseQuery } from './parseQuery'; import PullRequestData from './repo-pulls-data'; -import { PullRequestOrderField, OrderDirection, ParsedPullRequestQuery, Label } from './types'; +import { ParsedPullRequestQuery } from './types'; import { Pagination } from '../pagination/pagination'; import { PullRequestIssueTab } from '../pull-request-issue-tab/pull-request-issue-tab'; @@ -13,9 +13,11 @@ import { ClearFilterAndSortBtn } from '../clear-filter-and-sort-button'; import { useQuery } from '~/utils'; import { PULL_REQUEST_QUERY } from '~/utils/queries/pull-request'; import PullRequestContext, { PullRequestContextProps } from '~/context/pull-request-store'; -import { AUTH_TOKEN, GITHUB_GRAPHQL, DEFAULT_PAGE_SIZE } from '~/utils/constants'; +import { AUTH_TOKEN, GITHUB_GRAPHQL, DEFAULT_PAGE_SIZE, SEARCH_PULLS } from '~/utils/constants'; import DropdownContext from '~/context/issue-tab-header-dropdown'; import { sortOptions } from './data'; +import parseRestAPIPullRequests, { IPullRequestProps } from '~/utils/parseRestAPIPullRequests'; +import { OrderDirection, OrderField } from '~/utils/types'; export interface PullRequestsProps { owner: string; @@ -29,6 +31,7 @@ interface PullRequestsQueryParams { last?: number; after?: string; before?: string; + milestone?: string; labels?: string[]; orderBy: string; direction: string; @@ -40,68 +43,57 @@ export default component$(({ owner, name }: PullRequestsProps) => { const pullRequestStore = useContext(PullRequestContext); const dropdownStore = useContext(DropdownContext); - const afterCursor = typeof location.query.after === 'string' ? location.query.after : undefined; - const beforeCursor = typeof location.query.before === 'string' ? location.query.before : undefined; - - const hasActiveFilter = dropdownStore.selectedLabel || dropdownStore.selectedSort !== sortOptions[0].value; + const hasActiveFilter = + dropdownStore.selectedLabel || + dropdownStore.selectedSort !== sortOptions[0].value || + dropdownStore.selectedMilestones !== undefined; const resetFilters$ = $(() => { dropdownStore.selectedLabel = undefined; dropdownStore.selectedSort = sortOptions[0].value; + dropdownStore.selectedMilestones = undefined; + dropdownStore.selectedMilestoneNumber = undefined; navigate.path = `${location.pathname}?tab=${pullRequestStore.activeTab}`; }); - useClientEffect$(async () => { - pullRequestStore.loading = true; - const abortController = new AbortController(); - const response = await fetchRepoPullRequests( - { - owner, - name, - first: afterCursor || !beforeCursor ? DEFAULT_PAGE_SIZE : undefined, - last: beforeCursor ? DEFAULT_PAGE_SIZE : undefined, - labels: dropdownStore.selectedLabel ? [dropdownStore.selectedLabel] : undefined, - orderBy: PullRequestOrderField.CreatedAt, - direction: OrderDirection.Desc, - after: afterCursor, - before: beforeCursor, - }, - abortController - ); - - updatePullRequestState(pullRequestStore, parseQuery(response)); - }); - - useTask$(async ({ track }) => { - const abortController = new AbortController(); - const after = track(() => location.query.after); - const before = track(() => location.query.before); - track(() => pullRequestStore.activeTab); - track(() => dropdownStore.selectedSort); - track(() => dropdownStore.selectedLabel); - - // fetchRepoPullRequests needs auth token. - // Because we store auth token in sessionStorage we need to be sure that the storage is defined. - // We ask to the useTask to do the following operation only in browser, - // where we are sure that sessionStorage is not undefined. - if (isBrowser) { - const response = await fetchRepoPullRequests( - { - owner, - name, - after, - before, - first: location.query.after || !location.query.before ? DEFAULT_PAGE_SIZE : undefined, - last: location.query.before ? DEFAULT_PAGE_SIZE : undefined, - labels: dropdownStore.selectedLabel ? [dropdownStore.selectedLabel] : undefined, - orderBy: dropdownStore.selectedSort.split('^')[0], - direction: dropdownStore.selectedSort.split('^')[1], - }, - abortController - ); - updatePullRequestState(pullRequestStore, parseQuery(response)); - } - }); + useTask$( + async ({ track }) => { + const abortController = new AbortController(); + const after = track(() => location.query.after); + const before = track(() => location.query.before); + track(() => dropdownStore.selectedSort); + track(() => dropdownStore.selectedLabel); + track(() => dropdownStore.selectedMilestones); + + // fetchRepoPullRequests needs auth token. + // Because we store auth token in sessionStorage we need to be sure that the storage is defined. + // We ask to the useTask to do the following operation only in browser, + // where we are sure that sessionStorage is not undefined. + if (isBrowser) { + const response = await fetchRepoPullRequests( + { + owner, + name, + after, + before, + first: location.query.after || !location.query.before ? DEFAULT_PAGE_SIZE : undefined, + last: location.query.before ? DEFAULT_PAGE_SIZE : undefined, + labels: dropdownStore.selectedLabel ? [dropdownStore.selectedLabel] : undefined, + milestone: dropdownStore.selectedMilestones, + orderBy: dropdownStore.selectedSort.split('^')[0] || OrderField.CreatedAt, + direction: dropdownStore.selectedSort.split('^')[1] || OrderDirection.Desc, + }, + abortController + ); + if (dropdownStore.selectedMilestones) { + updatePullRequestState(pullRequestStore, response); + } else { + updatePullRequestState(pullRequestStore, parseQuery(response)); + } + } + }, + { eagerness: 'load' } + ); return ( <> @@ -115,6 +107,7 @@ export default component$(({ owner, name }: PullRequestsProps) => { tabType="pr" sortOption={sortOptions} labelOption={pullRequestStore.pullRequestLabels} + milestonesOption={pullRequestStore.pullRequestMilestones} openCount={pullRequestStore.openPullRequestCount} closedCount={pullRequestStore.closedPullRequestCount} /> @@ -152,41 +145,84 @@ export default component$(({ owner, name }: PullRequestsProps) => { }); export function updatePullRequestState(store: PullRequestContextProps, response: ParsedPullRequestQuery) { - const { closedPullRequests, openPullRequests, labels } = response; + const { closedPullRequests, openPullRequests, labels, milestones } = response; store.closedPullRequest = closedPullRequests.pullRequests; store.openPullRequest = openPullRequests.pullRequests; store.closedPullRequestCount = closedPullRequests.totalCount; store.openPullRequestCount = openPullRequests.totalCount; - store.pullRequestLabels = labels.map((lab: Label) => ({ label: lab.name, value: lab.name })); + store.pullRequestLabels = store.pullRequestLabels.length ? store.pullRequestLabels : labels; + store.pullRequestMilestones = store.pullRequestMilestones.length ? store.pullRequestMilestones : milestones; store.openPageInfo = openPullRequests.pageInfo; store.closedPageInfo = closedPullRequests.pageInfo; store.loading = false; } export async function fetchRepoPullRequests( - { owner, name, first, last, after, before, labels, orderBy, direction }: PullRequestsQueryParams, + { owner, name, first, last, after, before, labels, orderBy, direction, milestone }: PullRequestsQueryParams, abortController?: AbortController -): Promise { - const { executeQuery$ } = useQuery(PULL_REQUEST_QUERY); - const resp = await executeQuery$({ - signal: abortController?.signal, - url: GITHUB_GRAPHQL, - variables: { +) { + if (milestone) { + // + const { executeQuery$ } = useQuery(); + const pulls_data = { owner, name, - first, - last, - labels, - orderBy, + labels: labels?.[0] ?? undefined, + sort: orderBy, direction, - after, - before, - }, - headersOpt: { - Accept: 'application/vnd.github+json', - authorization: `Bearer ${sessionStorage.getItem(AUTH_TOKEN)}`, - }, - }); + first, + type: 'pull-request', + milestone, + }; + const restOpenPullRequests = await executeQuery$({ + url: SEARCH_PULLS({ + ...pulls_data, + state: 'open', + }), + headersOpt: { + authorization: `Bearer ${sessionStorage.getItem(AUTH_TOKEN)}`, + }, + }); + const restClosedPullRequests = await executeQuery$({ + url: SEARCH_PULLS({ + ...pulls_data, + state: 'closed', + }), + headersOpt: { + authorization: `Bearer ${sessionStorage.getItem(AUTH_TOKEN)}`, + }, + }); + const open = (await restOpenPullRequests.json()) as IPullRequestProps; + const closed = (await restClosedPullRequests.json()) as IPullRequestProps; + const openPullRequests = parseRestAPIPullRequests(open); + const closedPullRequests = parseRestAPIPullRequests(closed); + + return { + openPullRequests, + closedPullRequests, + }; + } else { + const { executeQuery$ } = useQuery(PULL_REQUEST_QUERY); + const resp = await executeQuery$({ + signal: abortController?.signal, + url: GITHUB_GRAPHQL, + variables: { + owner, + name, + first, + last, + labels, + orderBy, + direction, + after, + before, + }, + headersOpt: { + Accept: 'application/vnd.github+json', + authorization: `Bearer ${sessionStorage.getItem(AUTH_TOKEN)}`, + }, + }); - return await resp.json(); + return await resp.json(); + } } diff --git a/qwik-graphql-tailwind/src/components/repo-pulls/parseQuery.ts b/qwik-graphql-tailwind/src/components/repo-pulls/parseQuery.ts index dd4584bbb..47a4db740 100644 --- a/qwik-graphql-tailwind/src/components/repo-pulls/parseQuery.ts +++ b/qwik-graphql-tailwind/src/components/repo-pulls/parseQuery.ts @@ -1,6 +1,8 @@ -import { Label, ParsedPullRequestQuery, PullRequest, RepoPullRequestsQuery } from './types'; +import { parseMilestones } from '~/utils/helpers'; +import { ParsedPullRequest, ParsedPullRequestQuery, PullRequestProps, RepoPullRequestsQuery } from './types'; +import { Label } from '~/types'; -function parsePullRequests(connection?: any) { +function parsePullRequests(connection?: PullRequestProps): ParsedPullRequest { if (!connection) { return { pullRequests: [], @@ -13,46 +15,30 @@ function parsePullRequests(connection?: any) { const nodes = connection?.nodes || []; const totalCount = connection.totalCount; - const pullRequests = nodes.reduce((pullRequests: PullRequest[], pullRequest: any) => { - if (!pullRequest) { - return pullRequests; - } - + const pullRequests = nodes.map((pullRequest) => { const labelNodes = pullRequest.labels?.nodes || []; - const labels = labelNodes.reduce( - (labels: Label[], label: any) => - label - ? [ - ...labels, - { - color: label.color, - name: label.name, - }, - ] - : labels, - [] - ); + const labels = labelNodes.map((label: Label) => ({ + color: label.color, + name: label.name, + })); - return [ - ...pullRequests, - { - id: pullRequest.id, - login: pullRequest.author?.login, - commentCount: pullRequest.comments.totalCount, - labelCount: pullRequest.labels.totalCount, - labels, - closed: pullRequest.closed, - merged: pullRequest.merged, - title: pullRequest.title, - number: pullRequest.number, - createdAt: pullRequest.createdAt, - closedAt: pullRequest.closedAt, - mergedAt: pullRequest.mergedAt, - state: pullRequest.state, - url: pullRequest.url, - }, - ]; - }, []); + return { + id: pullRequest.id, + login: pullRequest.author?.login, + commentCount: pullRequest.comments.totalCount, + labelCount: pullRequest.labels.totalCount, + labels, + closed: pullRequest.closed, + merged: pullRequest.merged, + title: pullRequest.title, + number: pullRequest.number, + createdAt: pullRequest.createdAt, + closedAt: pullRequest.closedAt, + mergedAt: pullRequest.mergedAt, + state: pullRequest.state, + url: pullRequest.url, + }; + }); return { pullRequests, totalCount, pageInfo }; } @@ -60,23 +46,14 @@ function parsePullRequests(connection?: any) { export function parseQuery(data: { data: RepoPullRequestsQuery }): ParsedPullRequestQuery { const openPullRequests = parsePullRequests(data.data.repository?.openPullRequest); const closedPullRequests = parsePullRequests(data.data.repository?.closedPullRequest); + const milestones = parseMilestones(data.data.repository?.milestones); + const labels = data.data.repository?.labels; + const labelMap = (labels.nodes || []).map((label: Label) => ({ color: label.color, name: label.name })); - const labelMap = [...closedPullRequests.pullRequests, ...openPullRequests.pullRequests].reduce( - (acc: { [key: string]: Label }, issue: PullRequest) => { - const map: { [key: string]: Label } = {}; - issue.labels.forEach((label) => { - map[label.name] = label; - }); - return { - ...acc, - ...map, - }; - }, - {} - ); return { openPullRequests, closedPullRequests, - labels: Object.values(labelMap) as Label[], + labels: labelMap, + milestones, }; } diff --git a/qwik-graphql-tailwind/src/components/repo-pulls/types.ts b/qwik-graphql-tailwind/src/components/repo-pulls/types.ts index 7cdb3d1dd..1ac2dfe12 100644 --- a/qwik-graphql-tailwind/src/components/repo-pulls/types.ts +++ b/qwik-graphql-tailwind/src/components/repo-pulls/types.ts @@ -1,138 +1,42 @@ +import { PageInfo, Label, Milestone, MilestoneProps, LabelProps } from '~/types'; + export type RepoPullRequestsQuery = { - __typename?: 'Query'; - repository?: - | { - __typename?: 'Repository'; - openPullRequest: { - __typename?: 'PullRequestConnection'; - totalCount: number; - pageInfo: { - __typename?: 'PageInfo'; - hasPreviousPage: boolean; - hasNextPage: boolean; - startCursor?: string | null | undefined; - endCursor?: string | null | undefined; - }; - nodes?: - | Array< - | { - __typename?: 'PullRequest'; - id: string; - closed: boolean; - closedAt?: any | null | undefined; - merged: boolean; - mergedAt?: any | null | undefined; - title: string; - number: number; - createdAt: any; - author?: - | { __typename?: 'Bot'; login: string } - | { __typename?: 'EnterpriseUserAccount'; login: string } - | { __typename?: 'Mannequin'; login: string } - | { __typename?: 'Organization'; login: string } - | { __typename?: 'User'; login: string } - | null - | undefined; - comments: { - __typename?: 'IssueCommentConnection'; - totalCount: number; - }; - labels?: - | { - __typename?: 'LabelConnection'; - totalCount: number; - nodes?: - | Array< - | { - __typename?: 'Label'; - color: string; - name: string; - } - | null - | undefined - > - | null - | undefined; - } - | null - | undefined; - } - | null - | undefined - > - | null - | undefined; - }; - closedPullRequest: { - __typename?: 'PullRequestConnection'; - totalCount: number; - pageInfo: { - __typename?: 'PageInfo'; - hasPreviousPage: boolean; - hasNextPage: boolean; - startCursor?: string | null | undefined; - endCursor?: string | null | undefined; - }; - nodes?: - | Array< - | { - __typename?: 'PullRequest'; - id: string; - closed: boolean; - closedAt?: any | null | undefined; - merged: boolean; - mergedAt?: any | null | undefined; - title: string; - number: number; - createdAt: any; - author?: - | { __typename?: 'Bot'; login: string } - | { __typename?: 'EnterpriseUserAccount'; login: string } - | { __typename?: 'Mannequin'; login: string } - | { __typename?: 'Organization'; login: string } - | { __typename?: 'User'; login: string } - | null - | undefined; - comments: { - __typename?: 'IssueCommentConnection'; - totalCount: number; - }; - labels?: - | { - __typename?: 'LabelConnection'; - totalCount: number; - nodes?: - | Array< - | { - __typename?: 'Label'; - color: string; - name: string; - } - | null - | undefined - > - | null - | undefined; - } - | null - | undefined; - } - | null - | undefined - > - | null - | undefined; - }; - } - | null - | undefined; + repository: { + openPullRequest: PullRequestProps; + closedPullRequest: PullRequestProps; + milestones: MilestoneProps; + labels: LabelProps; + }; }; +export interface PullRequestNodeProps { + id: string; + state: string; + createdAt: string; + closedAt: string; + title: string; + author: { + login: string; + }; + url: string; + labels: { + totalCount: number; + nodes: Label[]; + }; + merged: boolean; + closed: boolean; + mergedAt?: string; + comments: { + totalCount: number; + }; + number: number; +} + export type PullRequest = { id: string; url: string; state: string; - comments: { + comments?: { totalCount: number; }; login: string; @@ -141,36 +45,24 @@ export type PullRequest = { closed: boolean; closedAt?: string | null; merged: boolean; - mergedAt?: Date | null; + mergedAt?: string | null; createdAt: string; labels: Label[]; commentCount: number; labelCount: number; }; -export enum PullRequestOrderField { - /** Order issues by comment count */ - Comments = 'COMMENTS', - /** Order issues by creation time */ - CreatedAt = 'CREATED_AT', - /** Order issues by update time */ - UpdatedAt = 'UPDATED_AT', -} - -export enum OrderDirection { - Asc = 'ASC', - Desc = 'DESC', -} - -export interface Label { - color: string; - name: string; +export interface PullRequestProps { + totalCount: number; + pageInfo: PageInfo; + nodes: PullRequestNodeProps[]; } export interface ParsedPullRequestQuery { openPullRequests: ParsedPullRequest; closedPullRequests: ParsedPullRequest; labels: Label[]; + milestones: Milestone[]; } export interface ParsedPullRequest { diff --git a/qwik-graphql-tailwind/src/components/top-repos/index.tsx b/qwik-graphql-tailwind/src/components/top-repos/index.tsx index c0c7598e9..3fb1c6380 100644 --- a/qwik-graphql-tailwind/src/components/top-repos/index.tsx +++ b/qwik-graphql-tailwind/src/components/top-repos/index.tsx @@ -49,7 +49,7 @@ export function updateTopRepos(store: RepoStore, response: any) { store.isLoading = false; } -export async function fetchTopRepos(abortController?: AbortController): Promise { +export async function fetchTopRepos(abortController?: AbortController) { const { executeQuery$ } = useQuery(TOP_REPOS_QUERY); const resp = await executeQuery$({ diff --git a/qwik-graphql-tailwind/src/components/user-repos/user-repos.tsx b/qwik-graphql-tailwind/src/components/user-repos/user-repos.tsx index dbe84371a..190495124 100644 --- a/qwik-graphql-tailwind/src/components/user-repos/user-repos.tsx +++ b/qwik-graphql-tailwind/src/components/user-repos/user-repos.tsx @@ -148,7 +148,7 @@ export function updateRepos(store: RepoStore, response: any) { export async function fetchRepos( { username, afterCursor, beforeCursor }: RepoQueryParams, abortController?: AbortController -): Promise { +) { const { executeQuery$ } = useQuery(REPOS_QUERY); const resp = await executeQuery$({ diff --git a/qwik-graphql-tailwind/src/context/issue-pr-store.ts b/qwik-graphql-tailwind/src/context/issue-pr-store.ts index c48c3e5c8..c2442b7e7 100644 --- a/qwik-graphql-tailwind/src/context/issue-pr-store.ts +++ b/qwik-graphql-tailwind/src/context/issue-pr-store.ts @@ -1,5 +1,6 @@ import { createContext } from '@builder.io/qwik'; -import { Label } from '../components/repo-pulls/types'; +import { Milestone } from '~/components/issue-tab-view/type'; +import { Label } from '~/types'; export type Tabs = 'open' | 'closed'; @@ -22,8 +23,8 @@ export interface IssuesPRContextProps { closedIssuesCount: number; openIssuesCount: number; loading: boolean; - milestones: { value: string; label: string }[]; - issuesLabel: { value: string; label: string }[]; + milestones: Milestone[]; + issuesLabel: Label[]; openPageInfo: { endCursor?: string; hasNextPage: boolean; diff --git a/qwik-graphql-tailwind/src/context/issue-tab-header-dropdown.ts b/qwik-graphql-tailwind/src/context/issue-tab-header-dropdown.ts index 9f7ebaf76..7be56e206 100644 --- a/qwik-graphql-tailwind/src/context/issue-tab-header-dropdown.ts +++ b/qwik-graphql-tailwind/src/context/issue-tab-header-dropdown.ts @@ -3,6 +3,7 @@ export interface DropdownStoresProps { selectedLabel?: string; selectedSort: string; selectedMilestones?: string; + selectedMilestoneNumber?: string; } const DropdownContext = createContext('DropdownStores-context'); diff --git a/qwik-graphql-tailwind/src/context/pull-request-store.ts b/qwik-graphql-tailwind/src/context/pull-request-store.ts index 853b628ff..14b6fa2f0 100644 --- a/qwik-graphql-tailwind/src/context/pull-request-store.ts +++ b/qwik-graphql-tailwind/src/context/pull-request-store.ts @@ -1,4 +1,5 @@ import { createContext } from '@builder.io/qwik'; +import { Milestone } from '~/components/issue-tab-view/type'; import { PullRequest } from '~/components/repo-pulls/types'; export type Tabs = 'open' | 'closed'; @@ -6,6 +7,7 @@ export type Tabs = 'open' | 'closed'; export interface Label { color: string; name: string; + description?: string; } export interface PullRequestContextProps { @@ -14,7 +16,8 @@ export interface PullRequestContextProps { openPullRequest: PullRequest[]; closedPullRequestCount: number; openPullRequestCount: number; - pullRequestLabels: { value: string; label: string }[]; + pullRequestLabels: Label[]; + pullRequestMilestones: Milestone[]; openPageInfo: { hasNextPage: boolean; hasPreviousPage: boolean; diff --git a/qwik-graphql-tailwind/src/routes/[owner]/[name]/index@named.tsx b/qwik-graphql-tailwind/src/routes/[owner]/[name]/index@named.tsx index 1a065074d..340f3ccd3 100644 --- a/qwik-graphql-tailwind/src/routes/[owner]/[name]/index@named.tsx +++ b/qwik-graphql-tailwind/src/routes/[owner]/[name]/index@named.tsx @@ -93,7 +93,7 @@ export async function fetchRepoTree( expression: string; }, abortController?: AbortController -): Promise { +) { const { executeQuery$ } = useQuery(REPO_TREE_QUERY); const resp = await executeQuery$({ @@ -124,7 +124,7 @@ export async function fetchRepoReadMe( expression: string; }, abortController?: AbortController -): Promise { +) { const { executeQuery$ } = useQuery(REPO_README_QUERY); const resp = await executeQuery$({ diff --git a/qwik-graphql-tailwind/src/routes/[owner]/[name]/layout-named.tsx b/qwik-graphql-tailwind/src/routes/[owner]/[name]/layout-named.tsx index 20baa8756..edc8debb1 100644 --- a/qwik-graphql-tailwind/src/routes/[owner]/[name]/layout-named.tsx +++ b/qwik-graphql-tailwind/src/routes/[owner]/[name]/layout-named.tsx @@ -94,7 +94,7 @@ export async function fetchRepoInfo( name: string; }, abortController?: AbortController -): Promise { +) { const { executeQuery$ } = useQuery(REPO_INFO_QUERY); const resp = await executeQuery$({ diff --git a/qwik-graphql-tailwind/src/routes/[owner]/[name]/pulls/index@named.tsx b/qwik-graphql-tailwind/src/routes/[owner]/[name]/pulls/index@named.tsx index 7492d94c7..3d552adce 100644 --- a/qwik-graphql-tailwind/src/routes/[owner]/[name]/pulls/index@named.tsx +++ b/qwik-graphql-tailwind/src/routes/[owner]/[name]/pulls/index@named.tsx @@ -15,6 +15,7 @@ export default component$(() => { closedPullRequestCount: 0, openPullRequestCount: 0, pullRequestLabels: [], + pullRequestMilestones: [], loading: true, openPageInfo: { hasNextPage: false, diff --git a/qwik-graphql-tailwind/src/routes/[owner]/[name]/tree/[branch]/[...path]/index@named.tsx b/qwik-graphql-tailwind/src/routes/[owner]/[name]/tree/[branch]/[...path]/index@named.tsx index e3ffb4838..de5c009b3 100644 --- a/qwik-graphql-tailwind/src/routes/[owner]/[name]/tree/[branch]/[...path]/index@named.tsx +++ b/qwik-graphql-tailwind/src/routes/[owner]/[name]/tree/[branch]/[...path]/index@named.tsx @@ -63,7 +63,7 @@ export async function fetchRepoTree( expression: string; }, abortController?: AbortController -): Promise { +) { const { executeQuery$ } = useQuery(REPO_TREE_QUERY); const resp = await executeQuery$({ diff --git a/qwik-graphql-tailwind/src/routes/[user]/index.tsx b/qwik-graphql-tailwind/src/routes/[user]/index.tsx index 744fd43a3..b64ae698b 100644 --- a/qwik-graphql-tailwind/src/routes/[user]/index.tsx +++ b/qwik-graphql-tailwind/src/routes/[user]/index.tsx @@ -77,10 +77,7 @@ export function updateUserProfile(store: UserStore, response: any) { store.user = response.data.user; } -export async function fetchUserProfile( - { username }: ProfileQueryParams, - abortController?: AbortController -): Promise { +export async function fetchUserProfile({ username }: ProfileQueryParams, abortController?: AbortController) { const { executeQuery$ } = useQuery(USER_PROFILE_QUERY); const resp = await executeQuery$({ diff --git a/qwik-graphql-tailwind/src/routes/layout.tsx b/qwik-graphql-tailwind/src/routes/layout.tsx index 40555e376..8009b0760 100644 --- a/qwik-graphql-tailwind/src/routes/layout.tsx +++ b/qwik-graphql-tailwind/src/routes/layout.tsx @@ -64,7 +64,7 @@ export function updateUserProfile(store: UserStore, response: any) { sessionStorage.setItem('user', JSON.stringify(response.data.viewer)); } -export async function fetchAuthenticatedUser(token: string | null, abortController?: AbortController): Promise { +export async function fetchAuthenticatedUser(token: string | null, abortController?: AbortController) { const { executeQuery$ } = useQuery(` query ViewerQuery { viewer { diff --git a/qwik-graphql-tailwind/src/routes/orgs/[name]/index.tsx b/qwik-graphql-tailwind/src/routes/orgs/[name]/index.tsx index 3aef118f4..198c2be8b 100644 --- a/qwik-graphql-tailwind/src/routes/orgs/[name]/index.tsx +++ b/qwik-graphql-tailwind/src/routes/orgs/[name]/index.tsx @@ -24,10 +24,7 @@ export default component$(() => { ); }); -export async function fetchOrgRepos( - { organization, first }: OrgRepoQueryParams, - abortController?: AbortController -): Promise { +export async function fetchOrgRepos({ organization, first }: OrgRepoQueryParams, abortController?: AbortController) { const { executeQuery$ } = useQuery(ORG_REPOS_QUERY); const resp = await executeQuery$({ diff --git a/qwik-graphql-tailwind/src/types/index.ts b/qwik-graphql-tailwind/src/types/index.ts new file mode 100644 index 000000000..492fcce1f --- /dev/null +++ b/qwik-graphql-tailwind/src/types/index.ts @@ -0,0 +1,31 @@ +export interface Milestone { + id: string; + closed: boolean; + description: string; + number: number; + title: string; +} + +export interface PageInfo { + startCursor?: string; + endCursor?: string; + hasNextPage: boolean; + hasPreviousPage: boolean; +} + +export interface Label { + color: string; + name: string; + description?: string; +} + +export interface MilestoneProps { + nodes: Milestone[]; + pageInfo: PageInfo; + totalCount: number; +} + +export interface LabelProps { + totalCount: number; + nodes: Label[]; +} diff --git a/qwik-graphql-tailwind/src/utils/constants.ts b/qwik-graphql-tailwind/src/utils/constants.ts index efe586af9..4772815dc 100644 --- a/qwik-graphql-tailwind/src/utils/constants.ts +++ b/qwik-graphql-tailwind/src/utils/constants.ts @@ -19,30 +19,63 @@ export const GET_TOKEN_URL = `${API_URL_BASE}/auth/token`; export const AUTH_TOKEN = 'token'; -// export const REPOS_URL = (page: string = '1') => -// `${GITHUB_URL_BASE}/user/repos?sort=pushed&affiliation=owner,collaborator&page=${page}`; -export const REPOS_URL = `${GITHUB_URL_BASE}/user/repos`; - -// export const SINGLE_USER_REPO = (user: string, repo: string) => `${GITHUB_URL_BASE}/repos/${user}/${repo}`; +export const DEFAULT_PAGE_SIZE = 30; -// export const ORG_REPO_LIST = (user: string) => `${GITHUB_URL_BASE}/orgs/${user}/repos?sort=pushed&per_page=10`; +const OrderFieldRest: Record = { + /** Order issues by comment count */ + COMMENTS: 'comments', + /** Order issues by creation time */ + CREATED_AT: 'created', + /** Order issues by update time */ + UPDATED_AT: 'updated', +}; -// export const USER_REPO_LIST = (user: string, page: string = '1') => -// `${GITHUB_URL_BASE}/users/${user}/repos?sort=pushed&page=${page}&type=all`; +export type State = 'open' | 'closed'; -// export const GISTS_URL = (user: string) => `${GITHUB_URL_BASE}/users/${user}/gists?per_page=10`; +export function convertObjectToQueryString(object: Record) { + return new URLSearchParams(object).toString(); +} -// export const PULLS_URL = (owner: string, repoName: string) => -// `${GITHUB_URL_BASE}/repos/${owner}/${repoName}/pulls?state=all`; +export function replaceSpaceWithPlus(str: string) { + return str.split(' ').join('+'); +} -// export const ISSUE_PR_SEARCH = ( -// user: string, -// repo: string, -// type: IssueType, -// state: State, -// per_page: number, -// page: number -// ) => -// `${GITHUB_URL_BASE}/search/issues?q=repo:${user}/${repo}%20is:${type}%20state:${state}&per_page=${per_page}&page=${page}`; +export const replaceEncodedSpaceWithPlus = (str: string) => { + return str.split(encodeURIComponent(' ')).join('+'); +}; -export const DEFAULT_PAGE_SIZE = 30; +export const SEARCH_PULLS = ({ + owner, + name, + first, + sort, + direction, + labels, + type, + milestone, + state, +}: { + owner: string; + name: string; + type: string; + first?: number; + sort?: string; + labels?: string; + direction?: string; + milestone?: string | number; + state: 'open' | 'closed'; +}) => { + const params = { + per_page: first?.toString() || DEFAULT_PAGE_SIZE.toString(), + sort: OrderFieldRest[sort || 'CREATED_AT'], + order: direction?.toLowerCase() || 'asc', + }; + const queryStrings = convertObjectToQueryString(params); + const milestone_check = `+milestone:"${ + typeof milestone === 'string' ? replaceEncodedSpaceWithPlus(encodeURIComponent(milestone)) : milestone + }"`; + const Q = `+is:${state}+is:${type}${labels ? `+label:"${replaceSpaceWithPlus(labels)}"` : ''}${ + milestone ? milestone_check : '' + }`; + return `${GITHUB_URL_BASE}/search/issues?q=repo:${owner}/${name}${Q}&${queryStrings}`; +}; diff --git a/qwik-graphql-tailwind/src/utils/dynamicColor.ts b/qwik-graphql-tailwind/src/utils/dynamicColor.ts new file mode 100644 index 000000000..239cd1e5f --- /dev/null +++ b/qwik-graphql-tailwind/src/utils/dynamicColor.ts @@ -0,0 +1,28 @@ +function getRGB(c: string | number) { + return typeof c === 'string' ? parseInt(c, 16) : c; +} + +function getsRGB(c: string | number) { + return getRGB(c) / 255 <= 0.03928 ? getRGB(c) / 255 / 12.92 : Math.pow((getRGB(c) / 255 + 0.055) / 1.055, 2.4); +} + +function getLuminance(hexColor: string) { + return ( + 0.2126 * getsRGB(hexColor.substr(1, 2)) + + 0.7152 * getsRGB(hexColor.substr(3, 2)) + + 0.0722 * getsRGB(hexColor.substr(-2)) + ); +} + +function getContrast(f: string, b: string) { + const L1 = getLuminance(f); + const L2 = getLuminance(b); + return (Math.max(L1, L2) + 0.1) / (Math.min(L1, L2) + 0.1); +} + +export function getTextColor(bgColor: string) { + const whiteContrast = getContrast(bgColor, '#ffffff'); + const blackContrast = getContrast(bgColor, '#000000'); + + return whiteContrast > blackContrast ? '#ffffff' : '#000000'; +} diff --git a/qwik-graphql-tailwind/src/utils/getMilestoneNumber.ts b/qwik-graphql-tailwind/src/utils/getMilestoneNumber.ts new file mode 100644 index 000000000..eb33e9eaf --- /dev/null +++ b/qwik-graphql-tailwind/src/utils/getMilestoneNumber.ts @@ -0,0 +1,4 @@ +import { Milestone } from '~/components/issue-tab-view/type'; + +export const getSelectedMilestoneNumber = (milestoneOptions: Milestone[], selectedMilestone?: string) => + selectedMilestone ? milestoneOptions.filter((mo) => mo.title === selectedMilestone)[0].number.toString() : undefined; diff --git a/qwik-graphql-tailwind/src/utils/helpers.ts b/qwik-graphql-tailwind/src/utils/helpers.ts index 3d4c33461..3842fa8ba 100644 --- a/qwik-graphql-tailwind/src/utils/helpers.ts +++ b/qwik-graphql-tailwind/src/utils/helpers.ts @@ -1,3 +1,5 @@ +import { MilestoneProps } from '~/types'; + export const cleanUpParams = (params: Record) => { if ('path' in params && params.path[params.path.length - 1] === '/') { params.path = params.path.slice(0, params.path.length - 1); @@ -6,3 +8,14 @@ export const cleanUpParams = (params: Record) => { }; export const getTime = (time: string) => new Date(time).getTime(); + +export function parseMilestones(milestones?: MilestoneProps) { + const nodes = milestones?.nodes || []; + return nodes.map((milestone) => ({ + id: milestone.id, + closed: milestone.closed, + title: milestone.title, + number: milestone.number, + description: milestone.description, + })); +} diff --git a/qwik-graphql-tailwind/src/utils/parseRestAPIPullRequests.ts b/qwik-graphql-tailwind/src/utils/parseRestAPIPullRequests.ts new file mode 100644 index 000000000..d65cf0e57 --- /dev/null +++ b/qwik-graphql-tailwind/src/utils/parseRestAPIPullRequests.ts @@ -0,0 +1,66 @@ +import { Label } from '~/types'; + +export interface IPulls { + id: number; + title: string; + number: string; + created_at: string; + closed_at: string; + user: { + login: string; + }; + state: 'open' | 'closed' | string; + merged_at: string | null; + url: string; + labels: Label[]; + comments: number; +} + +export interface IPullRequestProps { + incomplete_results: boolean; + total_count: number; + items: IPulls[]; +} + +export default function parseRestAPIPullRequests(connection?: IPullRequestProps) { + if (!connection) { + return { + pullRequests: [], + totalCount: 0, + pageInfo: { hasNextPage: false, hasPreviousPage: false }, + }; + } + + const pageInfo = { + hasNextPage: !connection.incomplete_results, + hasPreviousPage: connection.incomplete_results, + }; + const nodes = connection.items || []; + const totalCount = connection.total_count; + + const pullRequests = nodes.map((pullRequest) => { + const labels = (pullRequest.labels || []).map((label) => ({ + color: label.color, + name: label.name, + })); + + return { + id: pullRequest.id, + login: pullRequest.user?.login, + commentCount: pullRequest.comments, + labelCount: pullRequest.labels.length, + labels, + closed: Boolean(pullRequest.closed_at), + merged: Boolean(pullRequest.merged_at), + title: pullRequest.title, + number: pullRequest.number, + createdAt: pullRequest.created_at, + closedAt: pullRequest.closed_at, + mergedAt: pullRequest.merged_at, + state: pullRequest.state.toUpperCase(), + url: pullRequest.url, + }; + }); + + return { pullRequests, totalCount, pageInfo }; +} diff --git a/qwik-graphql-tailwind/src/utils/queries/issues-query.ts b/qwik-graphql-tailwind/src/utils/queries/issues-query.ts index 009693798..b5f142a31 100644 --- a/qwik-graphql-tailwind/src/utils/queries/issues-query.ts +++ b/qwik-graphql-tailwind/src/utils/queries/issues-query.ts @@ -17,6 +17,14 @@ export const ISSUES_QUERY = ` } totalCount } + labels(first: 100) { + totalCount + nodes { + color + name + description + } + } openIssues: issues( first: $first last: $last diff --git a/qwik-graphql-tailwind/src/utils/queries/pull-request.ts b/qwik-graphql-tailwind/src/utils/queries/pull-request.ts index 02142742d..13156ab9a 100644 --- a/qwik-graphql-tailwind/src/utils/queries/pull-request.ts +++ b/qwik-graphql-tailwind/src/utils/queries/pull-request.ts @@ -1,6 +1,29 @@ export const PULL_REQUEST_QUERY = ` query PullRequests($owner: String!, $name: String!, $first: Int, $last: Int, $before: String, $after: String, $labels: [String!], $orderBy: IssueOrderField!, $direction: OrderDirection!) { repository(owner: $owner, name: $name) { + milestones(first: 100, states: [OPEN]) { + nodes { + id + closed + description + number + title + } + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + totalCount + } + labels(first: 100) { + totalCount + nodes { + color + name + } + } openPullRequest: pullRequests(first: $first, last: $last, states: [OPEN], after: $after, before: $before, labels: $labels, orderBy:{ field: $orderBy, direction: $direction}) { totalCount pageInfo { diff --git a/qwik-graphql-tailwind/src/utils/types.ts b/qwik-graphql-tailwind/src/utils/types.ts index 184939cce..3b463838c 100644 --- a/qwik-graphql-tailwind/src/utils/types.ts +++ b/qwik-graphql-tailwind/src/utils/types.ts @@ -3,3 +3,17 @@ export * from './../components/repo-card/types'; export * from './../components/user-repos/types'; export * from './../components/icons/types'; export * from './../components/tab-navigation/types'; + +export enum OrderField { + /** Order issues by comment count */ + Comments = 'COMMENTS', + /** Order issues by creation time */ + CreatedAt = 'CREATED_AT', + /** Order issues by update time */ + UpdatedAt = 'UPDATED_AT', +} + +export enum OrderDirection { + Asc = 'ASC', + Desc = 'DESC', +} diff --git a/qwik-graphql-tailwind/src/utils/useQuery.ts b/qwik-graphql-tailwind/src/utils/useQuery.ts index 27006a1f2..fd27eb963 100644 --- a/qwik-graphql-tailwind/src/utils/useQuery.ts +++ b/qwik-graphql-tailwind/src/utils/useQuery.ts @@ -9,22 +9,27 @@ export interface QueryOptions { headersOpt?: HeadersInit; } -export const useQuery = (query: string) => { - const executeQuery$ = $( - async ({ url, signal, variables, headersOpt }: QueryOptions) => - await fetch(url, { - method: 'POST', - headers: { - ...headersOpt, - 'Content-Type': 'application/json', - }, - signal, - body: JSON.stringify({ - query, - variables, - }), - }) - ); +export const useQuery = (query?: string) => { + const executeQuery$ = $(async ({ url, signal, variables, headersOpt }: QueryOptions) => { + const fetchObj: { + headers: HeadersInit; + method?: string; + signal?: AbortSignal; + body?: string; + } = { + headers: { + ...headersOpt, + Accept: 'application/vnd.github+json', + 'Content-Type': 'application/json', + }, + }; + if (query) { + fetchObj['method'] = 'POST'; + fetchObj['signal'] = signal; + fetchObj['body'] = JSON.stringify({ query, variables }); + } + return await fetch(url, fetchObj); + }); return { executeQuery$ }; }; diff --git a/qwik-graphql-tailwind/vite.config.ts b/qwik-graphql-tailwind/vite.config.ts index 59ac32dfa..016867430 100644 --- a/qwik-graphql-tailwind/vite.config.ts +++ b/qwik-graphql-tailwind/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig(() => { return { plugins: [qwikCity(), qwikVite(), qwikReact(), tsconfigPaths()], optimizeDeps: { - include: ['date-fns', 'classnames', 'react-markdown', 'rehype-raw', 'remark-gfm', 'msw-storybook-addon', 'msw'] + include: ['date-fns', 'classnames', 'react-markdown', 'rehype-raw', 'remark-gfm', 'msw-storybook-addon', 'msw'], }, test: { // ...