diff --git a/destinations/airbyte-faros-destination/src/converters/jira/faros_issues.ts b/destinations/airbyte-faros-destination/src/converters/jira/faros_issues.ts index 613847aca..d5ad7f7d0 100644 --- a/destinations/airbyte-faros-destination/src/converters/jira/faros_issues.ts +++ b/destinations/airbyte-faros-destination/src/converters/jira/faros_issues.ts @@ -129,6 +129,16 @@ export class FarosIssues extends JiraConverter { }, }); + if (!this.useBoardOwnership(ctx)) { + results.push({ + model: 'tms_TaskBoardRelationship', + record: { + task: {uid: issue.key, source}, + board: {uid: issue.project, source}, + }, + }); + } + if (JiraCommon.normalize(issue.type) === 'epic') { results.push({ model: 'tms_Epic', diff --git a/sources/jira-source/resources/spec.json b/sources/jira-source/resources/spec.json index a1f1e12a8..90c1180fb 100644 --- a/sources/jira-source/resources/spec.json +++ b/sources/jira-source/resources/spec.json @@ -301,8 +301,16 @@ "description": "Use stateful board issue tracker to track issues on boards. This uses the Faros API to persist the state of boards between runs. Requires faros_source_id to be configured.", "default": false }, - "source_qualifier": { + "use_projects_as_boards": { "order": 38, + "type": "boolean", + "title": "Use Projects as Boards", + "description": "Deprecated: Use default behaviour (disabled) and sync Jira boards. When enabled, Jira project boards are not synced and each project has a single board with the project UID and all issues.", + "default": false, + "deprecated": true + }, + "source_qualifier": { + "order": 39, "type": "string", "title": "Source Qualifier", "description": "The qualifier to append as a suffix to the Jira source name to ensure uniqueness among entities with similar IDs when syncing multiple Jira instances, while preserving their original IDs, e.g. for Boards and Sprints.", diff --git a/sources/jira-source/src/index.ts b/sources/jira-source/src/index.ts index 3670370f8..67b620c74 100644 --- a/sources/jira-source/src/index.ts +++ b/sources/jira-source/src/index.ts @@ -150,6 +150,10 @@ export class JiraSource extends AirbyteSourceBase { if (config.fetch_teams) { streamNames.push(...TeamStreamNames); } + // If board ownership not enabled, remove the board issues stream. + if (config.use_projects_as_boards) { + streamNames.splice(streamNames.indexOf('faros_board_issues'), 1); + } const streams = catalog.streams.filter((stream) => streamNames.includes(stream.stream.name) ); diff --git a/sources/jira-source/src/jira.ts b/sources/jira-source/src/jira.ts index e24729370..93918aa7e 100644 --- a/sources/jira-source/src/jira.ts +++ b/sources/jira-source/src/jira.ts @@ -75,6 +75,7 @@ export interface JiraConfig extends AirbyteConfig, RoundRobinConfig { readonly requestedStreams?: Set; readonly use_sprints_reverse_search?: boolean; readonly use_faros_board_issue_tracker?: boolean; + readonly use_projects_as_boards?: boolean; readonly fetch_teams?: boolean; readonly organization_id?: string; readonly start_date?: string; @@ -977,6 +978,8 @@ export class Jira { return sprints; } + // add a get sprint and sprint report by project or smth like that + private getSprintsIterator( boardId: string, range?: [Date, Date] diff --git a/sources/jira-source/src/project-board-filter.ts b/sources/jira-source/src/project-board-filter.ts index 6763b018c..fd145052e 100644 --- a/sources/jira-source/src/project-board-filter.ts +++ b/sources/jira-source/src/project-board-filter.ts @@ -129,13 +129,17 @@ export class ProjectBoardFilter { // Ensure projects is populated await this.getProjects(); - // Ensure included / excluded boards are loaded - await this.loadSelectedBoards(); - - if (this.isWebhookSupplementMode() && this.hasFarosClient()) { - await this.getBoardsFromFaros(jira); + if (this.useProjectsAsBoards()) { + await this.getBoardsFromProjects(); } else { - await this.getBoardsFromJira(jira); + // Ensure included / excluded boards are loaded + await this.loadSelectedBoards(); + + if (this.isWebhookSupplementMode() && this.hasFarosClient()) { + await this.getBoardsFromFaros(jira); + } else { + await this.getBoardsFromJira(jira); + } } } return Array.from(this.boards.values()); @@ -214,6 +218,20 @@ export class ProjectBoardFilter { } } + /** + * Use the Jira API to get all boards for each project and use the UIDs to populate + * the internal boards map. + * Only includes boards based on the inclusion criteria determined by getBoardInclusion. + * With this method we generate a unique board per project to represent all tasks in the project + * + * @returns A Promise that resolves when all boards have been processed. + */ + private async getBoardsFromProjects(): Promise { + for (const project of this.projects) { + this.boards.set(project, {uid: project, issueSync: true}); + } + } + private async loadSelectedBoards(): Promise { if (this.loadedSelectedBoards) { return; @@ -251,4 +269,8 @@ export class ProjectBoardFilter { private hasFarosClient(): boolean { return Boolean(this.farosClient); } + + private useProjectsAsBoards(): boolean { + return this.config.use_projects_as_boards ?? false; + } } diff --git a/sources/jira-source/src/streams/faros_boards.ts b/sources/jira-source/src/streams/faros_boards.ts index e1a4ef5e3..0b3cb4214 100644 --- a/sources/jira-source/src/streams/faros_boards.ts +++ b/sources/jira-source/src/streams/faros_boards.ts @@ -24,6 +24,22 @@ export class FarosBoards extends StreamWithProjectSlices { const jira = await Jira.instance(this.config, this.logger); const projectKey = streamSlice?.project; + // If board ownership is disabled, return a virtual board for all tasks in the project. + if (this.config.use_projects_as_boards) { + const {included, issueSync} = + await this.projectBoardFilter.getBoardInclusion(projectKey); + if (included) { + yield { + uid: projectKey, + name: `Tasks in project ${projectKey}`, + projectKey, + type: 'Custom', + issueSync, + }; + } + return; + } + // Virtual board to represent all tasks in the project without a board yield { uid: `faros-tasks-with-no-board-${projectKey}`, diff --git a/sources/jira-source/src/streams/faros_sprints.ts b/sources/jira-source/src/streams/faros_sprints.ts index 541db47ff..d2d6bdd47 100644 --- a/sources/jira-source/src/streams/faros_sprints.ts +++ b/sources/jira-source/src/streams/faros_sprints.ts @@ -28,6 +28,7 @@ export class FarosSprints extends StreamWithBoardSlices { ): AsyncGenerator { const boardId = streamSlice.board; const jira = await Jira.instance(this.config, this.logger); + const board = await jira.getBoard(boardId); if (board.type !== 'scrum') return; const updateRange =