Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Execution curation #10342

Merged
merged 88 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
8b2b78d
wip: Execution annotation entities and migration
burivuhster Aug 7, 2024
bb364fc
wip: add indexes
burivuhster Aug 8, 2024
6fe3d7e
wip: typeorm entities
burivuhster Aug 8, 2024
5b1adea
wip: annotation tag endpoint
burivuhster Aug 8, 2024
94a0d35
wip: adding annotation tags to executions endpoint
burivuhster Aug 8, 2024
27020f4
wip: show annotation tags in executions list
burivuhster Aug 9, 2024
8eed096
wip: list of executions filtered by annotation tags
burivuhster Aug 9, 2024
7aa5e60
wip: remove temporary workaround
burivuhster Aug 9, 2024
afb7501
wip: endpoint for annotating an execution
burivuhster Aug 12, 2024
7f4db8f
wip: annotation endpoints
burivuhster Aug 12, 2024
01b17d2
wip: execution annotation sidebar
burivuhster Aug 13, 2024
f926413
wip: save execution rating annotation
burivuhster Aug 14, 2024
ed653da
wip: Refactor TagsDropdown and TagsManager
burivuhster Aug 14, 2024
7b59885
wip: annotation tags filter
burivuhster Aug 14, 2024
4a17f42
wip: Annotation tags manager modal
burivuhster Aug 14, 2024
b3f6e70
wip: annotation vote buttons
burivuhster Aug 14, 2024
7553ea5
wip: Refactor TagsContainer
burivuhster Aug 15, 2024
0fab0a9
wip: vote and tag annotation, updating UI after API call
burivuhster Aug 15, 2024
9d57b20
wip: no annotation data placeholder
burivuhster Aug 15, 2024
6a02c72
wip: minor fixes
burivuhster Aug 15, 2024
371127a
wip: clean up
burivuhster Aug 15, 2024
fa71264
wip: add unique annotation tag constraint + index
burivuhster Aug 15, 2024
aad5b90
Merge branch 'master' into ai-256-execution-curation-p0
burivuhster Aug 15, 2024
4c8e0ee
wip: clean up
burivuhster Aug 15, 2024
3d7902a
wip: clean up
burivuhster Aug 15, 2024
60a7f8b
wip: clean up
burivuhster Aug 15, 2024
f931c9e
wip: fix flaky test
burivuhster Aug 16, 2024
f6dc4c3
wip: fix flaky test
burivuhster Aug 16, 2024
13b0dc0
wip: fix tag input
burivuhster Aug 16, 2024
ea47286
wip: clean up
burivuhster Aug 16, 2024
b54e892
wip: Add filter by vote
burivuhster Aug 21, 2024
a5f5a3d
wip: hide execution annotation behind a feature flag
burivuhster Aug 21, 2024
592c5f6
wip: Handle annotation errors
burivuhster Aug 21, 2024
a53e2c1
wip: fix type issues
burivuhster Aug 21, 2024
4a0bf53
Merge branch 'master' into ai-256-execution-curation-p0
burivuhster Aug 21, 2024
fa41737
Merge branch 'master' into ai-256-execution-curation-p0
burivuhster Aug 21, 2024
18905cb
wip: addressing PR comments
burivuhster Aug 23, 2024
248958e
wip: addressing PR comments - parameters validation, types
burivuhster Aug 23, 2024
a1797de
wip: addressing PR comments - entity relation
burivuhster Aug 23, 2024
18d0c38
wip: addressing PR comments
burivuhster Aug 23, 2024
48d3874
wip: refactor fetching executions from db
burivuhster Aug 23, 2024
f6a0796
wip: clean up
burivuhster Aug 23, 2024
90f20b5
wip: fix possible SQL injection
burivuhster Aug 23, 2024
de0cc96
wip: fix types
burivuhster Aug 26, 2024
11bd6dc
wip: remove unnecessary external hooks
burivuhster Aug 26, 2024
457baa9
wip: refactor annotationTag.service
burivuhster Aug 26, 2024
ba7042f
wip: remove unused code
burivuhster Aug 26, 2024
cf05e6e
wip: UI/UX fixes
burivuhster Aug 26, 2024
4aa8c2d
wip: making annotate method single-purpose, checking permissions
burivuhster Aug 26, 2024
31377d2
wip: change annotation tags UI
burivuhster Aug 26, 2024
40a6cee
Merge branch 'master' into ai-256-execution-curation-p0
burivuhster Aug 27, 2024
eb8e77c
wip: fix broken test
burivuhster Aug 27, 2024
a0e10a6
wip: change migration version to up-to-date timestamp
burivuhster Aug 27, 2024
b412536
wip: fix broken import
burivuhster Aug 27, 2024
21e7e9a
wip: add index decorator
burivuhster Aug 27, 2024
4f8b34f
wip: addressing PR feedback
burivuhster Aug 27, 2024
961f0d4
wip: addressing PR feedback
burivuhster Aug 27, 2024
46817ee
wip: fix issue with aliases in left join query for MySQL
burivuhster Aug 27, 2024
406de1f
wip: different column title for annotation tags in tags manager modal
burivuhster Aug 27, 2024
8ad4dfc
wip: custom copy for no tags screen inside TagsManager
burivuhster Aug 27, 2024
79d64c8
wip: making tags in execution list non-interactible
burivuhster Aug 27, 2024
2b3312b
Update packages/editor-ui/src/components/TagsContainer.vue
burivuhster Aug 27, 2024
5387b2c
Update packages/editor-ui/src/components/TagsManager/TagsView/TagsVie…
burivuhster Aug 27, 2024
0f8e8e3
wip: addressing PR feedback
burivuhster Aug 27, 2024
b05a795
Merge branch 'ai-256-execution-curation-p0' of https://github.com/n8n…
burivuhster Aug 27, 2024
c1f9584
wip: addressing PR feedback
burivuhster Aug 27, 2024
0f6a4d3
wip: addressing PR feedback
burivuhster Aug 27, 2024
377f1e5
wip: addressing PR feedback
burivuhster Aug 27, 2024
71b2165
wip: addressing PR feedback
burivuhster Aug 27, 2024
34f70f2
wip: addressing PR feedback
burivuhster Aug 27, 2024
c55146b
Merge branch 'master' into ai-256-execution-curation-p0
burivuhster Aug 27, 2024
4327586
wip: follow the standard filename casing
burivuhster Aug 27, 2024
22394c3
wip: follow the standard filename case
burivuhster Aug 27, 2024
c4119e7
wip: UI/UX fixes
burivuhster Aug 28, 2024
cdf6aa4
Merge branch 'master' into ai-256-execution-curation-p0
burivuhster Aug 28, 2024
2d7b72a
wip: size down Rating label
burivuhster Aug 28, 2024
151e8a9
wip: Integration tests for execution annotation
burivuhster Aug 30, 2024
31441a4
Merge branch 'master' into ai-256-execution-curation-p0
burivuhster Aug 30, 2024
8f76e0e
wip: change file name casing
burivuhster Aug 30, 2024
25e4f79
wip: fix non-deterministic test
burivuhster Aug 30, 2024
520b987
wip: fix broken test
burivuhster Aug 30, 2024
6fb107e
wip: hide annotation feature if advanced execution filters aren't ena…
burivuhster Aug 30, 2024
693e0f4
wip: fix broken test
burivuhster Aug 30, 2024
0624a4e
wip: fix linter issues
burivuhster Sep 2, 2024
8ecbbbc
Merge branch 'master' into ai-256-execution-curation-p0
burivuhster Sep 2, 2024
2c87eb0
wip: fix integration test
burivuhster Sep 2, 2024
b9102a3
wip: exclude annotated executions from pruning
burivuhster Sep 2, 2024
1fe7ef4
wip: cleanup
burivuhster Sep 2, 2024
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
2 changes: 2 additions & 0 deletions cypress/e2e/20-workflow-executions.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,11 @@ const createMockExecutions = () => {
executionsTab.actions.createManualExecutions(5);
// Make some failed executions by enabling Code node with syntax error
executionsTab.actions.toggleNodeEnabled('Error');
workflowPage.getters.disabledNodes().should('have.length', 0);
executionsTab.actions.createManualExecutions(2);
// Then add some more successful ones
executionsTab.actions.toggleNodeEnabled('Error');
workflowPage.getters.disabledNodes().should('have.length', 1);
executionsTab.actions.createManualExecutions(4);
};

Expand Down
8 changes: 7 additions & 1 deletion cypress/e2e/5-ndv.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,13 @@ describe('NDV', () => {
ndv.getters.outputTableRow(1).find('mark').should('have.text', '<lib');

ndv.getters.outputDisplayMode().find('label').eq(1).should('include.text', 'JSON');
ndv.getters.outputDisplayMode().find('label').eq(1).click();
ndv.getters
.outputDisplayMode()
.find('label')
.eq(1)
.scrollIntoView()
.should('be.visible')
.click();

ndv.getters.outputDataContainer().find('.json-data').should('exist');
ndv.getters
Expand Down
1 change: 1 addition & 0 deletions packages/@n8n/permissions/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const DEFAULT_OPERATIONS = ['create', 'read', 'update', 'delete', 'list'] as const;
export const RESOURCES = {
annotationTag: [...DEFAULT_OPERATIONS] as const,
auditLogs: ['manage'] as const,
banner: ['dismiss'] as const,
communityPackage: ['install', 'uninstall', 'update', 'list', 'manage'] as const,
Expand Down
2 changes: 2 additions & 0 deletions packages/@n8n/permissions/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type ResourceScope<

export type WildcardScope = `${Resource}:*` | '*';

export type AnnotationTagScope = ResourceScope<'annotationTag'>;
export type AuditLogsScope = ResourceScope<'auditLogs', 'manage'>;
export type BannerScope = ResourceScope<'banner', 'dismiss'>;
export type CommunityPackageScope = ResourceScope<
Expand Down Expand Up @@ -44,6 +45,7 @@ export type WorkflowScope = ResourceScope<
>;

export type Scope =
| AnnotationTagScope
| AuditLogsScope
| BannerScope
| CommunityPackageScope
Expand Down
45 changes: 45 additions & 0 deletions packages/cli/src/controllers/annotation-tags.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Delete, Get, Patch, Post, RestController, GlobalScope } from '@/decorators';
import { AnnotationTagService } from '@/services/annotation-tag.service';
import { AnnotationTagsRequest } from '@/requests';

@RestController('/annotation-tags')
export class AnnotationTagsController {
constructor(private readonly annotationTagService: AnnotationTagService) {}

@Get('/')
@GlobalScope('annotationTag:list')
async getAll(req: AnnotationTagsRequest.GetAll) {
return await this.annotationTagService.getAll({
withUsageCount: req.query.withUsageCount === 'true',
});
}

@Post('/')
@GlobalScope('annotationTag:create')
async createTag(req: AnnotationTagsRequest.Create) {
const tag = this.annotationTagService.toEntity({ name: req.body.name });

return await this.annotationTagService.save(tag);
}

@Patch('/:id(\\w+)')
@GlobalScope('annotationTag:update')
async updateTag(req: AnnotationTagsRequest.Update) {
const newTag = this.annotationTagService.toEntity({
id: req.params.id,
name: req.body.name.trim(),
});

return await this.annotationTagService.save(newTag);
}

@Delete('/:id(\\w+)')
@GlobalScope('annotationTag:delete')
async deleteTag(req: AnnotationTagsRequest.Delete) {
const { id } = req.params;

await this.annotationTagService.delete(id);

return true;
}
}
1 change: 1 addition & 0 deletions packages/cli/src/databases/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const getSqliteConnectionOptions = (): SqliteConnectionOptions | SqlitePooledCon
database: path.resolve(Container.get(InstanceSettings).n8nFolder, sqliteConfig.database),
migrations: sqliteMigrations,
};

if (sqliteConfig.poolSize > 0) {
return {
type: 'sqlite-pooled',
Expand Down
20 changes: 20 additions & 0 deletions packages/cli/src/databases/entities/annotation-tag-entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Column, Entity, Index, ManyToMany, OneToMany } from '@n8n/typeorm';
import { IsString, Length } from 'class-validator';
import { WithTimestampsAndStringId } from './abstract-entity';
import type { ExecutionAnnotation } from '@/databases/entities/execution-annotation';
import type { AnnotationTagMapping } from '@/databases/entities/annotation-tag-mapping';

@Entity()
export class AnnotationTagEntity extends WithTimestampsAndStringId {
@Column({ length: 24 })
@Index({ unique: true })
@IsString({ message: 'Tag name must be of type string.' })
@Length(1, 24, { message: 'Tag name must be $constraint1 to $constraint2 characters long.' })
name: string;

@ManyToMany('ExecutionAnnotation', 'tags')
annotations: ExecutionAnnotation[];

@OneToMany('AnnotationTagMapping', 'tags')
annotationMappings: AnnotationTagMapping[];
}
23 changes: 23 additions & 0 deletions packages/cli/src/databases/entities/annotation-tag-mapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Entity, JoinColumn, ManyToOne, PrimaryColumn } from '@n8n/typeorm';
import type { ExecutionAnnotation } from './execution-annotation';
import type { AnnotationTagEntity } from './annotation-tag-entity';

/**
* This entity represents the junction table between the execution annotations and the tags
*/
@Entity({ name: 'execution_annotation_tags' })
export class AnnotationTagMapping {
@PrimaryColumn()
annotationId: number;

@ManyToOne('ExecutionAnnotation', 'tagMappings')
@JoinColumn({ name: 'annotationId' })
annotations: ExecutionAnnotation[];

@PrimaryColumn()
tagId: string;

@ManyToOne('AnnotationTagEntity', 'annotationMappings')
@JoinColumn({ name: 'tagId' })
tags: AnnotationTagEntity[];
}
61 changes: 61 additions & 0 deletions packages/cli/src/databases/entities/execution-annotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
Column,
Entity,
Index,
JoinColumn,
JoinTable,
ManyToMany,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
RelationId,
} from '@n8n/typeorm';
import { ExecutionEntity } from './execution-entity';
import type { AnnotationTagEntity } from './annotation-tag-entity';
import type { AnnotationTagMapping } from './annotation-tag-mapping';
import type { AnnotationVote } from 'n8n-workflow';

@Entity({ name: 'execution_annotations' })
export class ExecutionAnnotation {
@PrimaryGeneratedColumn()
id: number;

/**
* This field stores the up- or down-vote of the execution by user.
*/
@Column({ type: 'varchar', nullable: true })
vote: AnnotationVote | null;

/**
* Custom text note added to the execution by user.
*/
@Column({ type: 'varchar', nullable: true })
note: string | null;

@RelationId((annotation: ExecutionAnnotation) => annotation.execution)
executionId: string;

@Index({ unique: true })
@OneToOne('ExecutionEntity', 'annotation', {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'executionId' })
execution: ExecutionEntity;

@ManyToMany('AnnotationTagEntity', 'annotations')
@JoinTable({
name: 'execution_annotation_tags', // table name for the junction table of this relation
joinColumn: {
name: 'annotationId',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'tagId',
referencedColumnName: 'id',
},
})
tags?: AnnotationTagEntity[];

@OneToMany('AnnotationTagMapping', 'annotations')
tagMappings: AnnotationTagMapping[];
}
4 changes: 4 additions & 0 deletions packages/cli/src/databases/entities/execution-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { idStringifier } from '../utils/transformers';
import type { ExecutionData } from './execution-data';
import type { ExecutionMetadata } from './execution-metadata';
import { WorkflowEntity } from './workflow-entity';
import type { ExecutionAnnotation } from '@/databases/entities/execution-annotation';

@Entity()
@Index(['workflowId', 'id'])
Expand Down Expand Up @@ -65,6 +66,9 @@ export class ExecutionEntity {
@OneToOne('ExecutionData', 'execution')
executionData: Relation<ExecutionData>;

@OneToOne('ExecutionAnnotation', 'execution')
annotation?: Relation<ExecutionAnnotation>;

@ManyToOne('WorkflowEntity')
workflow: WorkflowEntity;
}
6 changes: 6 additions & 0 deletions packages/cli/src/databases/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@ import { WorkflowHistory } from './workflow-history';
import { Project } from './project';
import { ProjectRelation } from './project-relation';
import { InvalidAuthToken } from './invalid-auth-token';
import { AnnotationTagEntity } from './annotation-tag-entity';
import { AnnotationTagMapping } from './annotation-tag-mapping';
import { ExecutionAnnotation } from './execution-annotation';

export const entities = {
AnnotationTagEntity,
AnnotationTagMapping,
AuthIdentity,
AuthProviderSyncHistory,
AuthUser,
CredentialsEntity,
EventDestinations,
ExecutionAnnotation,
ExecutionEntity,
InstalledNodes,
InstalledPackages,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { MigrationContext, ReversibleMigration } from '@/databases/types';

const annotationsTableName = 'execution_annotations';
const annotationTagsTableName = 'annotation_tag_entity';
const annotationTagMappingsTableName = 'execution_annotation_tags';

export class CreateAnnotationTables1724753530828 implements ReversibleMigration {
async up({ schemaBuilder: { createTable, column } }: MigrationContext) {
await createTable(annotationsTableName)
.withColumns(
column('id').int.notNull.primary.autoGenerate,
column('executionId').int.notNull,
column('vote').varchar(6),
column('note').text,
)
.withIndexOn('executionId', true)
.withForeignKey('executionId', {
tableName: 'execution_entity',
columnName: 'id',
onDelete: 'CASCADE',
}).withTimestamps;

await createTable(annotationTagsTableName)
.withColumns(column('id').varchar(16).primary.notNull, column('name').varchar(24).notNull)
.withIndexOn('name', true).withTimestamps;

await createTable(annotationTagMappingsTableName)
.withColumns(column('annotationId').int.notNull, column('tagId').varchar(24).notNull)
.withForeignKey('annotationId', {
tableName: annotationsTableName,
columnName: 'id',
onDelete: 'CASCADE',
})
.withIndexOn('tagId')
.withIndexOn('annotationId')
.withForeignKey('tagId', {
tableName: annotationTagsTableName,
columnName: 'id',
onDelete: 'CASCADE',
});
}

async down({ schemaBuilder: { dropTable } }: MigrationContext) {
await dropTable(annotationTagMappingsTableName);
await dropTable(annotationTagsTableName);
await dropTable(annotationsTableName);
}
}
2 changes: 2 additions & 0 deletions packages/cli/src/databases/migrations/mysqldb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { AddActivatedAtUserSetting1717498465931 } from './1717498465931-AddActiv
import { RefactorExecutionIndices1723796243146 } from '../common/1723796243146-RefactorExecutionIndices';
import { AddConstraintToExecutionMetadata1720101653148 } from '../common/1720101653148-AddConstraintToExecutionMetadata';
import { CreateInvalidAuthTokenTable1723627610222 } from '../common/1723627610222-CreateInvalidAuthTokenTable';
import { CreateAnnotationTables1724753530828 } from '../common/1724753530828-CreateExecutionAnnotationTables';

export const mysqlMigrations: Migration[] = [
InitialMigration1588157391238,
Expand Down Expand Up @@ -125,4 +126,5 @@ export const mysqlMigrations: Migration[] = [
AddConstraintToExecutionMetadata1720101653148,
CreateInvalidAuthTokenTable1723627610222,
RefactorExecutionIndices1723796243146,
CreateAnnotationTables1724753530828,
];
2 changes: 2 additions & 0 deletions packages/cli/src/databases/migrations/postgresdb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { RefactorExecutionIndices1723796243146 } from '../common/1723796243146-R
import { AddConstraintToExecutionMetadata1720101653148 } from '../common/1720101653148-AddConstraintToExecutionMetadata';
import { FixExecutionMetadataSequence1721377157740 } from './1721377157740-FixExecutionMetadataSequence';
import { CreateInvalidAuthTokenTable1723627610222 } from '../common/1723627610222-CreateInvalidAuthTokenTable';
import { CreateAnnotationTables1724753530828 } from '../common/1724753530828-CreateExecutionAnnotationTables';

export const postgresMigrations: Migration[] = [
InitialMigration1587669153312,
Expand Down Expand Up @@ -125,4 +126,5 @@ export const postgresMigrations: Migration[] = [
FixExecutionMetadataSequence1721377157740,
CreateInvalidAuthTokenTable1723627610222,
RefactorExecutionIndices1723796243146,
CreateAnnotationTables1724753530828,
];
2 changes: 2 additions & 0 deletions packages/cli/src/databases/migrations/sqlite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import { AddActivatedAtUserSetting1717498465931 } from './1717498465931-AddActiv
import { RefactorExecutionIndices1723796243146 } from '../common/1723796243146-RefactorExecutionIndices';
import { AddConstraintToExecutionMetadata1720101653148 } from '../common/1720101653148-AddConstraintToExecutionMetadata';
import { CreateInvalidAuthTokenTable1723627610222 } from '../common/1723627610222-CreateInvalidAuthTokenTable';
import { CreateAnnotationTables1724753530828 } from '../common/1724753530828-CreateExecutionAnnotationTables';

const sqliteMigrations: Migration[] = [
InitialMigration1588102412422,
Expand Down Expand Up @@ -119,6 +120,7 @@ const sqliteMigrations: Migration[] = [
AddConstraintToExecutionMetadata1720101653148,
CreateInvalidAuthTokenTable1723627610222,
RefactorExecutionIndices1723796243146,
CreateAnnotationTables1724753530828,
];

export { sqliteMigrations };
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Service } from 'typedi';
import { DataSource, Repository } from '@n8n/typeorm';
import { AnnotationTagMapping } from '@/databases/entities/annotation-tag-mapping';

@Service()
export class AnnotationTagMappingRepository extends Repository<AnnotationTagMapping> {
constructor(dataSource: DataSource) {
super(AnnotationTagMapping, dataSource.manager);
}

/**
* Overwrite annotation tags for the given execution. Annotation should already exist.
*/
async overwriteTags(annotationId: number, tagIds: string[]) {
return await this.manager.transaction(async (tx) => {
await tx.delete(AnnotationTagMapping, { annotationId });

const tagMappings = tagIds.map((tagId) => ({
annotationId,
tagId,
}));

return await tx.insert(AnnotationTagMapping, tagMappings);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Service } from 'typedi';
import { DataSource, Repository } from '@n8n/typeorm';
import { AnnotationTagEntity } from '@/databases/entities/annotation-tag-entity';

@Service()
export class AnnotationTagRepository extends Repository<AnnotationTagEntity> {
constructor(dataSource: DataSource) {
super(AnnotationTagEntity, dataSource.manager);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Service } from 'typedi';
import { DataSource, Repository } from '@n8n/typeorm';
import { ExecutionAnnotation } from '@/databases/entities/execution-annotation';

@Service()
export class ExecutionAnnotationRepository extends Repository<ExecutionAnnotation> {
constructor(dataSource: DataSource) {
super(ExecutionAnnotation, dataSource.manager);
}
}
Loading
Loading