Skip to content
Draft
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
Expand Up @@ -436,6 +436,7 @@ export default function ItemInvestigation(props: {
/>
<ItemInvestigationRuleResults
itemIdentifier={{ id: item.id, typeId: item.type.id }}
itemTypes={(allItemTypes as GQLItemType[] | undefined) ?? []}
submissionTime={item.submissionTime?.toString()}
rules={allRules ?? []}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import { describe, expect, it, vi } from 'vitest';

import { useGQLInvestigationItemsQuery } from '../../../graphql/generated';
import ItemInvestigationRuleResults from './ItemInvestigationRuleResults';

vi.mock('../../../graphql/generated', async (importOriginal) => ({
...(await importOriginal<typeof import('../../../graphql/generated')>()),
useGQLInvestigationItemsQuery: vi.fn(),
useGQLMatchingBankNamesQuery: vi.fn(() => ({
loading: false,
error: undefined,
data: undefined,
})),
}));

const execution = (
outcome: 'PASSED' | 'FAILED',
timestamp: string,
conditionDetail: string,
) => ({
__typename: 'RuleExecutionResult',
date: timestamp,
ts: timestamp,
contentId: 'content',
itemTypeName: 'Post',
itemTypeId: 'post',
content: '{}',
environment: 'LIVE',
passed: outcome === 'PASSED',
ruleId: 'repeated-rule',
ruleName: 'Repeated Rule',
policies: [],
tags: [],
result: {
__typename: 'ConditionSetWithResult',
conjunction: 'AND',
conditions: [
{
__typename: 'LeafConditionWithResult',
input: {
__typename: 'ConditionInputField',
type: 'CONTENT_FIELD',
name: conditionDetail,
},
comparator: 'EQUALS',
result: { __typename: 'ConditionResult', outcome },
},
],
result: { __typename: 'ConditionResult', outcome },
},
});

describe('ItemInvestigationRuleResults', () => {
it('shows the stored result for the selected execution', () => {
vi.mocked(useGQLInvestigationItemsQuery).mockReturnValue({
loading: false,
error: undefined,
data: {
__typename: 'Query',
itemWithHistory: {
__typename: 'ItemHistoryResult',
item: {
__typename: 'ContentItem',
id: 'item',
submissionId: 'submission',
type: { __typename: 'ContentItemType', id: 'post' },
},
executions: [
execution(
'FAILED',
'2026-01-01T20:11:00.000Z',
'Older execution field',
),
execution(
'PASSED',
'2026-01-01T20:13:00.000Z',
'Newer execution field',
),
],
},
},
} as unknown as ReturnType<typeof useGQLInvestigationItemsQuery>);

render(
<MemoryRouter>
<ItemInvestigationRuleResults
itemIdentifier={{ id: 'item', typeId: 'post' }}
itemTypes={[]}
rules={[]}
/>
</MemoryRouter>,
);

const failedRow = screen
.getAllByRole('row')
.find((row) => within(row).queryByText('Did Not Match'))!;
userEvent.click(failedRow);

const dialog = screen.getByRole('dialog');
expect(within(dialog).getByText('Rule Result: Repeated Rule')).toBeTruthy();
const outcome = within(dialog).getByText('Outcome:').parentElement;
expect(outcome?.textContent).toContain('Did Not Match');
expect(outcome?.textContent).not.toContain('Matched');
expect(within(dialog).getByText('Older execution field')).toBeTruthy();
expect(within(dialog).queryByText('Newer execution field')).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,48 @@ import Table from '../components/table/Table';

import {
GQLConditionOutcome,
GQLInvestigationItemsQuery,
GQLItemType,
useGQLInvestigationItemsQuery,
} from '../../../graphql/generated';
import { ReadonlyDeep } from '../../../utils/typescript-types';
import { LookbackVersion } from '../rules/info/insights/RuleInsightsSamplesTable';
import RuleInsightsSampleDetailResults, {
import {
getDisplayName,
outcomeIcon,
RuleInsightsSampleDetailResultsImpl,
} from '../rules/info/insights/sample_details/RuleInsightsSampleDetailResults';
import type { ConditionSetWithResult } from '../rules/types';
import InvestigationTag from './InvestigationTag';

type InvestigationExecution = Extract<
GQLInvestigationItemsQuery['itemWithHistory'],
{ readonly __typename: 'ItemHistoryResult' }
>['executions'][number];
type InvestigationExecutionResult = InvestigationExecution['result'];

export default function ItemInvestigationRuleResults(props: {
itemIdentifier: ItemIdentifier;
itemTypes: readonly GQLItemType[];
submissionTime?: string;
rules: Readonly<ReadonlyDeep<{ id: string; actions: { name: string }[] }>[]>;
}) {
const { rules, itemIdentifier, submissionTime } = props;
const { rules, itemIdentifier, submissionTime, itemTypes } = props;
const navigate = useNavigate();
const [modalInfo, setModalInfo] = useState<
| {
visible: false;
title: undefined;
ruleId: undefined;
contentId: undefined;
result: undefined;
}
| {
visible: true;
title: string;
ruleId: string;
contentId: string;
result: InvestigationExecutionResult;
}
>({
visible: false,
title: undefined,
ruleId: undefined,
contentId: undefined,
result: undefined,
});

const {
Expand Down Expand Up @@ -250,8 +257,7 @@ export default function ItemInvestigationRuleResults(props: {
setModalInfo({
visible: false,
title: undefined,
ruleId: undefined,
contentId: undefined,
result: undefined,
});

const modal = (
Expand All @@ -262,28 +268,27 @@ export default function ItemInvestigationRuleResults(props: {
>
{modalInfo.visible && (
<div className="p-4">
<RuleInsightsSampleDetailResults
ruleId={modalInfo.ruleId}
itemIdentifier={itemIdentifier}
itemSubmissionDate={submissionTime}
lookback={LookbackVersion.LATEST}
/>
{modalInfo.result ? (
<RuleInsightsSampleDetailResultsImpl
itemTypes={itemTypes}
conditionSetWithResult={
modalInfo.result as unknown as ConditionSetWithResult
}
loading={false}
/>
) : (
<div className="m-2 text-red-500">Rule result is unavailable</div>
)}
</div>
)}
</CoopModal>
);

const onSelectRow = (rowData: Row<any>) => {
const executionResult = ruleExecutionsHistory[rowData.index];
if (executionResult == null) {
return;
}

setModalInfo({
visible: true,
title: `Rule Result: ${rowData.original.rule}`,
ruleId: executionResult.ruleId,
contentId: executionResult.contentId,
result: rowData.original.ruleExecutionResult,
});
};

Expand Down
Loading