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
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, expect, it } from 'vitest';
import type { PullRequestUser } from '@shared/pull-requests';
import { usersWithLoginFirst } from './pr-filter-items';

function user(userName: string): PullRequestUser {
return {
userId: `id-${userName}`,
userName,
displayName: userName,
avatarUrl: null,
url: null,
userUpdatedAt: null,
userCreatedAt: null,
};
}

describe('usersWithLoginFirst', () => {
it('moves the current GitHub user to the front', () => {
const users = [user('anna'), user('dominik'), user('zoe')];

expect(usersWithLoginFirst(users, 'dominik').map((item) => item.userName)).toEqual([
'dominik',
'anna',
'zoe',
]);
});

it('matches the current GitHub user case-insensitively', () => {
const users = [user('anna'), user('Dominik'), user('zoe')];

expect(usersWithLoginFirst(users, 'dominik').map((item) => item.userName)).toEqual([
'Dominik',
'anna',
'zoe',
]);
});

it('preserves order when the current GitHub user is unavailable', () => {
const users = [user('anna'), user('zoe')];

expect(usersWithLoginFirst(users, 'dominik').map((item) => item.userName)).toEqual([
'anna',
'zoe',
]);
});
Comment thread
mezotv marked this conversation as resolved.

it('preserves order when no GitHub login is available', () => {
const users = [user('anna'), user('zoe')];

expect(usersWithLoginFirst(users, null).map((item) => item.userName)).toEqual(['anna', 'zoe']);
expect(usersWithLoginFirst(users, undefined).map((item) => item.userName)).toEqual([
'anna',
'zoe',
]);
});

it('preserves order when the current GitHub user is already first', () => {
const users = [user('dominik'), user('anna'), user('zoe')];

expect(usersWithLoginFirst(users, 'dominik').map((item) => item.userName)).toEqual([
'dominik',
'anna',
'zoe',
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { PullRequestUser } from '@shared/pull-requests';

export type UserItem = { value: string; label: string; avatarUrl?: string };

export function toUserItem(user: PullRequestUser): UserItem {
return {
value: user.userId,
label: user.displayName ?? user.userName,
avatarUrl: user.avatarUrl ?? undefined,
};
}

export function usersWithLoginFirst(
users: ReadonlyArray<PullRequestUser>,
login?: string | null
): PullRequestUser[] {
if (!login) return [...users];

const normalizedLogin = login.toLowerCase();
const currentUserIndex = users.findIndex(
(user) => user.userName.toLowerCase() === normalizedLogin
);
if (currentUserIndex === -1) return [...users];

const currentUser = users[currentUserIndex];
return [currentUser, ...users.slice(0, currentUserIndex), ...users.slice(currentUserIndex + 1)];
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { CheckIcon, ChevronDownIcon, Github, RefreshCw, X } from 'lucide-react';
import { observer } from 'mobx-react-lite';
import { motion } from 'motion/react';
import { useState } from 'react';
import type { UserItem } from '@renderer/features/projects/components/pr-view/pr-filter-items';
import {
usePrViewState,
type LabelItem,
type StatusFilter,
type UserItem,
} from '@renderer/features/projects/components/pr-view/usePrViewState';
import { getRepositoryStore } from '@renderer/features/projects/stores/project-selectors';
import { useNavigate, useParams } from '@renderer/lib/layout/navigation-provider';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ import { useEffect, useMemo, useState } from 'react';
import { getPrSyncStore } from '@renderer/features/projects/stores/project-selectors';
import { useDebounce } from '@renderer/lib/hooks/useDebounce';
import { rpc } from '@renderer/lib/ipc';
import { useGithubContext } from '@renderer/lib/providers/github-context-provider';
import type { PrFilters, PrSortField } from '@shared/pull-requests';
import { toUserItem, usersWithLoginFirst, type UserItem } from './pr-filter-items';
import { useFilterOptions, usePullRequests } from './usePullRequests';

export type StatusFilter = 'open' | 'not-open';

export type UserItem = { value: string; label: string; avatarUrl?: string };
export type LabelItem = { value: string; label: string; color?: string };

export function usePrViewState(projectId: string, repositoryUrl: string | null) {
const queryClient = useQueryClient();
const { user } = useGithubContext();
const [statusFilter, setStatusFilter] = useState<StatusFilter>('open');
const [sortFilter, setSortFilter] = useState<PrSortField>('newest');
const [selectedAuthorUserId, setSelectedAuthorUserId] = useState<string | null>(null);
Expand Down Expand Up @@ -46,21 +48,14 @@ export function usePrViewState(projectId: string, repositoryUrl: string | null)

const authorItems: UserItem[] = useMemo(
() =>
(filterOptions?.authors ?? []).map((a) => ({
value: a.userId,
label: a.displayName ?? a.userName,
avatarUrl: a.avatarUrl ?? undefined,
})),
[filterOptions?.authors]
usersWithLoginFirst(filterOptions?.authors ?? [], user?.login).map((author) =>
toUserItem(author)
),
[filterOptions?.authors, user?.login]
);

const assigneeItems: UserItem[] = useMemo(
() =>
(filterOptions?.assignees ?? []).map((a) => ({
value: a.userId,
label: a.displayName ?? a.userName,
avatarUrl: a.avatarUrl ?? undefined,
})),
() => (filterOptions?.assignees ?? []).map((assignee) => toUserItem(assignee)),
[filterOptions?.assignees]
);

Expand Down
Loading