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
11 changes: 10 additions & 1 deletion static/app/types/group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -523,9 +523,18 @@ type SuggestedOwner = {
type: SuggestedOwnerReason;
};

/**
* Mirrors OwnershipRuleOwnerResponse from the backend
*/
interface OwnershipRuleOwner {
name: string;
type: 'user' | 'team';
id?: string;
}

export interface ParsedOwnershipRule {
matcher: {pattern: string; type: string};
owners: Actor[];
owners: OwnershipRuleOwner[];
}

export type IssueOwnership = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,32 @@ describe('OwnershipRulesTable', () => {
expect(screen.getByRole('button', {name: 'Everyone'})).toBeInTheDocument();
});

it('should render owners without an id', async () => {
const rules: ParsedOwnershipRule[] = [
{
matcher: {pattern: 'src/app/*', type: 'path'},
owners: [{type: 'user', name: 'unresolved-user@example.com'}],
},
{
matcher: {pattern: 'src/utils/*', type: 'path'},
owners: [
{type: 'user', id: user1.id, name: user1.name},
{type: 'team', name: 'backend'},
],
},
];

render(<OwnershipRulesTable projectRules={rules} codeowners={[]} />);

// Clear the "My Teams" filter to see all rules
await userEvent.click(screen.getByRole('button', {name: 'My Teams'}));
await userEvent.click(screen.getByRole('button', {name: 'Clear'}));

expect(screen.getByText('src/app/*')).toBeInTheDocument();
expect(screen.getByText('unresolved-user@example.com')).toBeInTheDocument();
expect(screen.getByText('src/utils/*')).toBeInTheDocument();
});

it('should paginate results', async () => {
const owners: Actor[] = [{type: 'user', id: user1.id, name: user1.name}];
const rules: ParsedOwnershipRule[] = new Array(100).fill(0).map((_, i) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import {IconChevron} from 'sentry/icons';
import {t, tn} from 'sentry/locale';
import {MemberListStore} from 'sentry/stores/memberListStore';
import {TeamStore} from 'sentry/stores/teamStore';
import type {Actor} from 'sentry/types/core';
import type {ParsedOwnershipRule} from 'sentry/types/group';
import type {CodeOwner} from 'sentry/types/integrations';
import {defined} from 'sentry/utils';
import {useTeams} from 'sentry/utils/useTeams';
import {useUser} from 'sentry/utils/useUser';
import {OwnershipOwnerFilter} from 'sentry/views/settings/project/projectOwnership/ownershipOwnerFilter';
Expand Down Expand Up @@ -84,6 +86,10 @@ export function OwnershipRulesTable({
const myTeams = useMemo(() => {
const memberTeamsIds = teams.filter(team => team.isMember).map(team => team.id);
return allActors.filter(actor => {
if (!defined(actor.id)) {
return false;
}

if (actor.type === 'user') {
return actor.id === user.id;
}
Expand Down Expand Up @@ -140,7 +146,7 @@ export function OwnershipRulesTable({
<RulesTableWrapper data-test-id="ownership-rules-table">
<Flex align="center" gap="xl">
<OwnershipOwnerFilter
actors={allActors}
actors={allActors.filter((actor): actor is Actor => defined(actor.id))}
selectedTeams={selectedActors ?? []}
handleChangeFilter={handleChangeFilter}
isMyTeams={
Expand All @@ -166,7 +172,11 @@ export function OwnershipRulesTable({
emptyMessage={t('No ownership rules found')}
>
{chunkedRules[page]?.map((rule, index) => {
const hasUnknownOwners = rule.owners.some(owner => !defined(owner.id));
const ownerNames = rule.owners.map(owner => {
if (!owner.id) {
return owner.name;
}
if (owner.type === 'team') {
const team = TeamStore.getById(owner.id);
return team?.slug ? `#${team.slug}` : owner.name;
Expand All @@ -185,12 +195,15 @@ export function OwnershipRulesTable({
<RowRule>{rule.matcher.pattern}</RowRule>
<Flex align="center" gap="md">
<AvatarContainer numAvatars={Math.min(rule.owners.length, 3)}>
<SuggestedAvatarStack
owners={rule.owners}
suggested={false}
reverse={false}
tooltip={ownerNames.join(', ')}
/>
{/* Avoid attempting to render the avatar stack if there are broken owners */}
{!hasUnknownOwners && (
<SuggestedAvatarStack
owners={rule.owners as Actor[]}
suggested={false}
reverse={false}
tooltip={ownerNames.join(', ')}
/>
)}
</AvatarContainer>
{name}
{rule.owners.length > 1 &&
Expand Down
Loading