Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const config: CodegenConfig = {
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
},
// GitHub's live schema currently fails graphql-js's stricter
// interface-deprecation-consistency validation (added in graphql v17).
// Skip validation so introspection can still succeed.
assumeValid: true,
},
},
documents: ['src/renderer/utils/forges/github/**/*.graphql'],
Expand Down
19 changes: 19 additions & 0 deletions src/renderer/components/metrics/IssueTypesPill.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { screen } from '@testing-library/react';

import { renderWithProviders } from '../../__helpers__/test-utils';

import { IconColor } from '../../types';

import { IssueTypesPill } from './IssueTypesPill';

describe('renderer/components/metrics/IssueTypesPill.tsx', () => {
it('renders nothing when no type provided', () => {
const tree = renderWithProviders(<IssueTypesPill />);
expect(tree.container).toBeEmptyDOMElement();
});

it('renders a pill for the native issue type', () => {
renderWithProviders(<IssueTypesPill issueType={{ name: 'Bug', color: IconColor.RED }} />);
expect(screen.getByRole('button')).toBeInTheDocument();
});
});
19 changes: 19 additions & 0 deletions src/renderer/components/metrics/IssueTypesPill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { FC } from 'react';

import { IssueOpenedIcon } from '@primer/octicons-react';

import type { GitifyIssueType } from '../../types';

import { MetricPill } from './MetricPill';

export interface IssueTypesPillProps {
issueType?: GitifyIssueType;
}

export const IssueTypesPill: FC<IssueTypesPillProps> = ({ issueType }) => {
if (!issueType) {
return null;
}

return <MetricPill color={issueType.color} contents={issueType.name} icon={IssueOpenedIcon} />;
};
40 changes: 40 additions & 0 deletions src/renderer/components/metrics/MetricGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { renderWithProviders } from '../../__helpers__/test-utils';
import { mockGitifyNotification } from '../../__mocks__/notifications-mocks';
import { mockSettings } from '../../__mocks__/state-mocks';

import { IconColor } from '../../types';

import { MetricGroup, type MetricGroupProps } from './MetricGroup';

describe('renderer/components/metrics/MetricGroup.tsx', () => {
Expand Down Expand Up @@ -30,4 +32,42 @@ describe('renderer/components/metrics/MetricGroup.tsx', () => {

expect(tree.container).toMatchSnapshot();
});

it('should render the issue type pill when the subject has a native issue type', async () => {
const props: MetricGroupProps = {
notification: {
...mockGitifyNotification,
subject: {
...mockGitifyNotification.subject,
issueType: { name: 'Bug', color: IconColor.RED },
},
},
};

const tree = renderWithProviders(<MetricGroup {...props} />, {
settings: { ...mockSettings, showPills: true },
});

expect(tree.getByText('Bug')).toBeInTheDocument();
});

it('should render the stacked PR pill when the subject is part of a stack', async () => {
const props: MetricGroupProps = {
notification: {
...mockGitifyNotification,
subject: {
...mockGitifyNotification.subject,
isStacked: true,
stackPosition: 2,
stackDepth: 3,
},
},
};

const tree = renderWithProviders(<MetricGroup {...props} />, {
settings: { ...mockSettings, showPills: true },
});

expect(tree.getByText('2/3')).toBeInTheDocument();
});
});
10 changes: 10 additions & 0 deletions src/renderer/components/metrics/MetricGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { useSettingsStore } from '../../stores';
import type { GitifyNotification } from '../../types';

import { CommentsPill } from './CommentsPill';
import { IssueTypesPill } from './IssueTypesPill';
import { LabelsPill } from './LabelsPill';
import { LinkedIssuesPill } from './LinkedIssuesPill';
import { MilestonePill } from './MilestonePill';
import { ReactionsPill } from './ReactionsPill';
import { ReviewsPill } from './ReviewsPill';
import { StackedPrsPill } from './StackedPrsPill';

export interface MetricGroupProps {
notification: GitifyNotification;
Expand All @@ -24,8 +26,16 @@ export const MetricGroup: FC<MetricGroupProps> = ({ notification }) => {

return (
<div className="flex gap-1">
<IssueTypesPill issueType={notification.subject.issueType} />

<LinkedIssuesPill linkedIssues={notification.subject.linkedIssues ?? []} />

<StackedPrsPill
isStacked={notification.subject.isStacked}
stackPosition={notification.subject.stackPosition}
stackDepth={notification.subject.stackDepth}
/>

<ReactionsPill
reactionGroups={notification.subject.reactionGroups ?? []}
reactionsCount={notification.subject.reactionsCount ?? 0}
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/metrics/MetricPill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { type IconColor, Size } from '../../types';

export interface MetricPillProps {
contents: string | ReactNode;
metric?: number;
metric?: string | number;
icon: Icon;
color: IconColor;
}
Expand Down
26 changes: 26 additions & 0 deletions src/renderer/components/metrics/StackedPrsPill.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { renderWithProviders } from '../../__helpers__/test-utils';

import { StackedPrsPill } from './StackedPrsPill';

describe('renderer/components/metrics/StackedPrsPill.tsx', () => {
it('renders nothing when not stacked', () => {
const tree = renderWithProviders(<StackedPrsPill />);
expect(tree.container).toBeEmptyDOMElement();
});

it('renders a pill when stacked', () => {
const tree = renderWithProviders(<StackedPrsPill isStacked stackPosition={1} stackDepth={2} />);
expect(tree.getByText('1/2')).toBeInTheDocument();
});

it('renders a pill with no position/depth metric when position is missing', () => {
const tree = renderWithProviders(<StackedPrsPill isStacked stackDepth={2} />);
// Only the tooltip contents, with no metric text alongside it
expect(tree.container.textContent).toBe('Part of a stacked PR series');
});

it('renders the metric for the first position in a stack', () => {
const tree = renderWithProviders(<StackedPrsPill isStacked stackDepth={3} stackPosition={0} />);
expect(tree.getByText('0/3')).toBeInTheDocument();
});
});
35 changes: 35 additions & 0 deletions src/renderer/components/metrics/StackedPrsPill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { FC } from 'react';

import { GitMergeIcon } from '@primer/octicons-react';

import { IconColor } from '../../types';

import { MetricPill } from './MetricPill';

export interface StackedPrsPillProps {
isStacked?: boolean;
stackPosition?: number;
stackDepth?: number;
}

export const StackedPrsPill: FC<StackedPrsPillProps> = ({
isStacked,
stackPosition,
stackDepth,
}) => {
if (!isStacked) {
return null;
}

const metric =
stackPosition != null && stackDepth != null ? `${stackPosition}/${stackDepth}` : undefined;

return (
<MetricPill
color={IconColor.YELLOW}
contents="Part of a stacked PR series"
icon={GitMergeIcon}
metric={metric}
/>
);
};
14 changes: 14 additions & 0 deletions src/renderer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,14 @@ export interface GitifySubject {
commentCount?: number;
/** Labels names and colors */
labels?: GitifyLabels[];
/** Whether the PR is part of a GitHub native stacked PR series */
isStacked?: boolean;
/** This PR's 1-indexed position in the stack, when part of a stacked PR series */
stackPosition?: number;
/** Total number of PRs in the stack, when part of a stacked PR series */
stackDepth?: number;
/** GitHub-native issue type (e.g. Bug, Feature, Task) */
issueType?: GitifyIssueType;
/** Milestone state/title */
milestone?: GitifyMilestone;
/** Deep link to notification thread */
Expand Down Expand Up @@ -463,6 +471,12 @@ export interface GitifyNotificationDisplay {
defaultUserType: UserType;
}

/** GitHub-native issue type, normalized to a Gitify icon color token */
export interface GitifyIssueType {
name: string;
color: IconColor;
}

export type GitifyMilestone = MilestoneFieldsFragment;

export type GitifyReactionGroup = ReactionGroupFieldsFragment;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export function mockIssueResponseNode(mocks: {
labels: { nodes: [] },
comments: { totalCount: 0, nodes: [] },
milestone: null,
issueType: null,
reactions: {
totalCount: 0,
},
Expand Down Expand Up @@ -142,6 +143,7 @@ export function mockPullRequestResponseNode(mocks: {
closingIssuesReferences: {
nodes: [],
},
stackEntry: null,
reactions: {
totalCount: 0,
},
Expand Down
Loading