Skip to content

Commit f7fb513

Browse files
committed
Expose Tools.usages
1 parent 9b2fc17 commit f7fb513

File tree

6 files changed

+111
-0
lines changed

6 files changed

+111
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { type ID } from '~/common';
2+
import {
3+
type DataLoaderStrategy,
4+
LoaderFactory,
5+
type LoaderOptionsOf,
6+
} from '~/core/data-loader';
7+
import { type Tool } from '../tool/dto';
8+
import { type ToolUsage } from './dto';
9+
import { ToolUsageService } from './tool-usage.service';
10+
11+
export interface UsagesByTool {
12+
tool: Tool;
13+
usages: readonly ToolUsage[];
14+
}
15+
16+
@LoaderFactory()
17+
export class ToolUsageByToolLoader
18+
implements DataLoaderStrategy<UsagesByTool, Tool, ID>
19+
{
20+
constructor(private readonly usages: ToolUsageService) {}
21+
22+
getOptions() {
23+
return {
24+
propertyKey: ({ tool }) => tool,
25+
cacheKeyFn: (tool) => tool.id,
26+
} satisfies LoaderOptionsOf<ToolUsageByToolLoader>;
27+
}
28+
29+
async loadMany(tools: readonly Tool[]): Promise<readonly UsagesByTool[]> {
30+
return await this.usages.readManyForTools(tools);
31+
}
32+
}

src/components/tools/tool-usage/tool-usage.gel.repository.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,18 @@ export class ToolUsageRepository
3838
}));
3939
},
4040
);
41+
42+
async listForTools(tools: readonly ID[]) {
43+
return await this.db.run(this.listForToolsQuery, { tools });
44+
}
45+
private readonly listForToolsQuery = e.params(
46+
{ tools: e.array(e.uuid) },
47+
($) => {
48+
const tools = e.cast(e.Tool, e.array_unpack($.tools));
49+
return e.select(tools, (tool) => ({
50+
tool: e.select(tool, (c) => ({ id: c.id })),
51+
usages: e.select(tool.usages, this.hydrate),
52+
}));
53+
},
54+
);
4155
}

src/components/tools/tool-usage/tool-usage.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@ import { splitDb } from '~/core/database';
33
import { ToolCoreModule } from '../tool/tool.module';
44
import { ResourceToolsResolver } from './resource-tools.resolver';
55
import { ToolUsageByContainerLoader } from './tool-usage-by-container.loader';
6+
import { ToolUsageByToolLoader } from './tool-usage-by-tool.loader';
67
import { ToolUsageRepository as GelRepository } from './tool-usage.gel.repository';
78
import { ToolUsageLoader } from './tool-usage.loader';
89
import { ToolUsageRepository as Neo4jRepository } from './tool-usage.neo4j.repository';
910
import { ToolUsageResolver } from './tool-usage.resolver';
1011
import { ToolUsageService } from './tool-usage.service';
12+
import { ToolUsagesResolver } from './tool-usages.resolver';
1113

1214
@Module({
1315
imports: [ToolCoreModule],
1416
providers: [
1517
ToolUsageResolver,
1618
ResourceToolsResolver,
19+
ToolUsagesResolver,
1720
ToolUsageLoader,
1821
ToolUsageByContainerLoader,
22+
ToolUsageByToolLoader,
1923
ToolUsageService,
2024
splitDb(Neo4jRepository, GelRepository),
2125
],

src/components/tools/tool-usage/tool-usage.neo4j.repository.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,29 @@ export class ToolUsageRepository extends DtoRepository(ToolUsage) {
6969
return result;
7070
}
7171

72+
async listForTools(tools: readonly ID[]) {
73+
const result = await this.db
74+
.query()
75+
.unwind([...tools], 'toolId')
76+
.match(node('tool', 'Tool', { id: variable('toolId') }))
77+
.subQuery('tool', (sub) =>
78+
sub
79+
.match([
80+
node('node', 'ToolUsage'),
81+
relation('out', '', 'tool', ACTIVE),
82+
node('tool'),
83+
])
84+
.subQuery('node', this.hydrate())
85+
.return(collect('dto').as('usages')),
86+
)
87+
.return<{
88+
tool: { id: ID };
89+
usages: ReadonlyArray<UnsecuredDto<ToolUsage>>;
90+
}>(['tool { .id }', 'usages'])
91+
.run();
92+
return result;
93+
}
94+
7295
async create(input: CreateToolUsage) {
7396
const initialProps = {
7497
startDate: input.startDate,

src/components/tools/tool-usage/tool-usage.service.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Privileges } from '../../authorization';
1515
import { Tool } from '../tool/dto';
1616
import { type CreateToolUsage, ToolUsage, type UpdateToolUsage } from './dto';
1717
import { type UsagesByContainer } from './tool-usage-by-container.loader';
18+
import { type UsagesByTool } from './tool-usage-by-tool.loader';
1819
import { ToolUsageRepository } from './tool-usage.neo4j.repository';
1920

2021
type TypedResource = Resource & { __typename: string };
@@ -62,6 +63,23 @@ export class ToolUsageService {
6263
});
6364
}
6465

66+
async readManyForTools(tools: readonly Tool[]) {
67+
const toolsById = mapKeys.fromList(tools, (t) => t.id).asMap;
68+
const rows = await this.repo.listForTools(tools.map((t) => t.id));
69+
return await Promise.all(
70+
rows.map(async (row): Promise<UsagesByTool> => {
71+
const tool = toolsById.get(row.tool.id)!;
72+
const usages = await Promise.all(
73+
row.usages.map(async (dto) => {
74+
const container = await this.loadContainer(dto.container);
75+
return this.secure(dto, container) ?? [];
76+
}),
77+
);
78+
return { tool, usages: usages.flat() };
79+
}),
80+
);
81+
}
82+
6583
private secure(
6684
dto: UnsecuredDto<ToolUsage>,
6785
container: Resource,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
2+
import { Loader, type LoaderOf } from '@seedcompany/data-loader';
3+
import { Tool } from '../tool/dto';
4+
import { ToolUsage } from './dto';
5+
import { ToolUsageByToolLoader } from './tool-usage-by-tool.loader';
6+
7+
@Resolver(() => Tool)
8+
export class ToolUsagesResolver {
9+
@ResolveField(() => [ToolUsage], {
10+
description: 'The usages of this tool',
11+
})
12+
async usages(
13+
@Parent() tool: Tool,
14+
@Loader(() => ToolUsageByToolLoader)
15+
loader: LoaderOf<ToolUsageByToolLoader>,
16+
): Promise<readonly ToolUsage[]> {
17+
const { usages } = await loader.load(tool);
18+
return usages;
19+
}
20+
}

0 commit comments

Comments
 (0)