Skip to content

Commit 1d32d03

Browse files
committed
feat: show the count when you click on a selected resource
1 parent 1bfd8b5 commit 1d32d03

File tree

5 files changed

+158
-7
lines changed

5 files changed

+158
-7
lines changed

src/ElectronBackend/api/__tests__/queries.test.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,99 @@ describe('searchResources', () => {
4848
});
4949
});
5050

51+
describe('resourceDescendantCount', () => {
52+
beforeEach(async () => {
53+
await initializeDb({
54+
metadata: { projectId: '', fileCreationDate: '' },
55+
resources: pathsToResources(['/parent/target/child', '/parent/sibling']),
56+
config: { classifications: {} },
57+
manualAttributions: {
58+
attributions: {},
59+
resourcesToAttributions: {},
60+
attributionsToResources: {},
61+
},
62+
externalAttributions: {
63+
attributions: {
64+
'attr-target': { id: 'attr-target', criticality: 0 },
65+
'attr-child': { id: 'attr-child', criticality: 0 },
66+
},
67+
resourcesToAttributions: {
68+
'/parent/target': ['attr-target'],
69+
'/parent/target/child': ['attr-child'],
70+
},
71+
attributionsToResources: {
72+
'attr-target': ['/parent/target'],
73+
'attr-child': ['/parent/target/child'],
74+
},
75+
},
76+
frequentLicenses: { nameOrder: [], texts: {} },
77+
resolvedExternalAttributions: new Set(),
78+
attributionBreakpoints: new Set(),
79+
filesWithChildren: new Set(),
80+
baseUrlsForSources: {},
81+
externalAttributionSources: {},
82+
});
83+
});
84+
85+
it('counts the resource itself and all its descendants', async () => {
86+
const { result } = await queries.resourceDescendantCount({
87+
searchString: '',
88+
resourcePath: '/parent/target',
89+
});
90+
91+
expect(result).toBe(2);
92+
});
93+
94+
it('filters descendants by search string', async () => {
95+
const { result } = await queries.resourceDescendantCount({
96+
searchString: 'child',
97+
resourcePath: '/parent/target',
98+
});
99+
100+
expect(result).toBe(1);
101+
});
102+
103+
it('filters descendants to those with a given attribution', async () => {
104+
const { result } = await queries.resourceDescendantCount({
105+
searchString: '',
106+
resourcePath: '/parent/target',
107+
onAttributions: ['attr-target'],
108+
});
109+
110+
expect(result).toBe(1);
111+
});
112+
113+
it('counts all resources matching any of the given attributions', async () => {
114+
const { result } = await queries.resourceDescendantCount({
115+
searchString: '',
116+
resourcePath: '/parent/target',
117+
onAttributions: ['attr-target', 'attr-child'],
118+
});
119+
120+
expect(result).toBe(2);
121+
});
122+
123+
it('returns 0 when no descendants match the given attributions', async () => {
124+
const { result } = await queries.resourceDescendantCount({
125+
searchString: '',
126+
resourcePath: '/parent/target',
127+
onAttributions: ['non-existent'],
128+
});
129+
130+
expect(result).toBe(0);
131+
});
132+
133+
it('applies searchString and onAttributions as a combined filter', async () => {
134+
const { result } = await queries.resourceDescendantCount({
135+
searchString: 'child',
136+
resourcePath: '/parent/target',
137+
onAttributions: ['attr-target'],
138+
});
139+
140+
expect(result).toBe(0);
141+
});
142+
});
143+
51144
describe('filterCounts', () => {
52145
async function setupDb(options?: { resolved?: Array<string> }) {
53146
await initializeDb({

src/ElectronBackend/api/queries.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,37 @@ export const queries = {
4747
return { result: result.map((r) => r.path) };
4848
},
4949

50+
async resourceDescendantCount(props: {
51+
searchString: string;
52+
resourcePath: string;
53+
onAttributions?: Array<string>;
54+
}) {
55+
const db = getDb();
56+
const resource = await getResourceOrThrow(db, props.resourcePath);
57+
let query = getDb()
58+
.selectFrom('resource')
59+
.select((eb) => eb.fn.countAll<number>().as('total'))
60+
.where('id', '>=', resource.id)
61+
.where('id', '<=', resource.max_descendant_id)
62+
.where('path', 'like', `%${props.searchString}%`);
63+
if (props.onAttributions !== undefined) {
64+
const onAttributions = props.onAttributions;
65+
query = query.where((eb) =>
66+
eb(
67+
'id',
68+
'in',
69+
eb
70+
.selectFrom('resource_to_attribution')
71+
.select('resource_id')
72+
.where('attribution_uuid', 'in', onAttributions),
73+
),
74+
);
75+
}
76+
const result = await query.executeTakeFirstOrThrow();
77+
78+
return { result: result.total };
79+
},
80+
5081
async getAttributionData(props: { attributionUuid: string }) {
5182
const result = await getDb()
5283
.selectFrom('attribution')

src/ElectronBackend/api/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
CaseWhenBuilder,
77
ExpressionBuilder,
88
expressionBuilder,
9+
Kysely,
910
sql,
1011
Transaction,
1112
} from 'kysely';
@@ -134,7 +135,7 @@ export function removeTrailingSlash(path: string) {
134135
}
135136

136137
export async function getResourceOrThrow(
137-
trx: Transaction<DB>,
138+
trx: Kysely<DB>,
138139
resourcePath: string,
139140
) {
140141
const strippedResourcePath = removeTrailingSlash(resourcePath);

src/Frontend/Components/ResourceBrowser/ResourceBrowser.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import { useCallback, useMemo } from 'react';
88
import { AllowedFrontendChannels } from '../../../shared/ipc-channels';
99
import { text } from '../../../shared/text';
1010
import { useAppSelector } from '../../state/hooks';
11-
import { getResourceIdsOfSelectedAttribution } from '../../state/selectors/resource-selectors';
11+
import {
12+
getResourceIdsOfSelectedAttribution,
13+
getSelectedAttributionId,
14+
getSelectedResourceId,
15+
} from '../../state/selectors/resource-selectors';
1216
import { useIsSelectedAttributionVisible } from '../../state/variables/use-filtered-data';
1317
import { usePanelSizes } from '../../state/variables/use-panel-sizes';
1418
import { useVariable } from '../../state/variables/use-variable';
@@ -42,6 +46,13 @@ export function ResourceBrowser() {
4246
{ searchString: debouncedSearchAll },
4347
{ placeholderData: keepPreviousData },
4448
).data ?? [];
49+
const selectedResourceId = useAppSelector(getSelectedResourceId);
50+
const selectedCount: number =
51+
backend.resourceDescendantCount.useQuery(
52+
{ searchString: debouncedSearchAll, resourcePath: selectedResourceId },
53+
{ placeholderData: keepPreviousData },
54+
).data ?? 0;
55+
4556
const linkedResourcesFiltered = useMemo(
4657
() =>
4758
isSelectedAttributionVisible
@@ -56,6 +67,17 @@ export function ResourceBrowser() {
5667
],
5768
);
5869

70+
const selectedAttributionId = useAppSelector(getSelectedAttributionId);
71+
const selectedLinkedCount: number =
72+
backend.resourceDescendantCount.useQuery(
73+
{
74+
searchString: debouncedSearchLinked,
75+
resourcePath: selectedResourceId,
76+
onAttributions: [selectedAttributionId],
77+
},
78+
{ placeholderData: keepPreviousData },
79+
).data ?? 0;
80+
5981
const setWidth = useCallback(
6082
(width: number) => setPanelSizes({ resourceBrowserWidth: width }),
6183
[setPanelSizes],
@@ -82,7 +104,10 @@ export function ResourceBrowser() {
82104
setWidth={setWidth}
83105
setHeight={setHeight}
84106
upperPanel={{
85-
title: text.resourceBrowser.allResources(allResourcesFiltered.length),
107+
title: text.resourceBrowser.allResources(
108+
selectedCount,
109+
allResourcesFiltered.length,
110+
),
86111
search: {
87112
value: searchAll,
88113
setValue: setSearchAll,
@@ -93,6 +118,7 @@ export function ResourceBrowser() {
93118
}}
94119
lowerPanel={{
95120
title: text.resourceBrowser.linkedResources(
121+
selectedLinkedCount,
96122
linkedResourcesFiltered.length,
97123
),
98124
search: {

src/shared/text.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,10 @@ export const text = {
227227
discard: 'Discard and Proceed',
228228
},
229229
resourceBrowser: {
230-
allResources: (count: number) =>
231-
`All Resources (${new Intl.NumberFormat().format(count)})`,
232-
linkedResources: (count: number) =>
233-
`Linked Resources (${new Intl.NumberFormat().format(count)})`,
230+
allResources: (selectedResources: number, totalCount: number) =>
231+
`Resources (${new Intl.NumberFormat().format(selectedResources)} / ${new Intl.NumberFormat().format(totalCount)})`,
232+
linkedResources: (selectedResources: number, totalCount: number) =>
233+
`Linked Resources (${new Intl.NumberFormat().format(selectedResources)} / ${new Intl.NumberFormat().format(totalCount)})`,
234234
hasHighlyCriticalSignals: 'Has highly critical signals',
235235
hasMediumCriticalSignals: 'Has medium critical signals',
236236
hasSignals: 'Has signals',

0 commit comments

Comments
 (0)