diff --git a/libs/event-emitter/src/issue-enhancers/tag-styled-enhancer.ts b/libs/event-emitter/src/issue-enhancers/tag-styled-enhancer.ts new file mode 100644 index 0000000..9b63b76 --- /dev/null +++ b/libs/event-emitter/src/issue-enhancers/tag-styled-enhancer.ts @@ -0,0 +1,87 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +import { Injectable, Logger } from '@nestjs/common'; +import { RedmineTypes } from '../models/redmine-types'; +import { IssueEnhancerInterface } from './issue-enhancer-interface'; + +export namespace TagStyledEnhancerNs { + /** + * * key - tag name, + * * value - css style for tag + */ + export type Styles = Record; + + export type StyledTag = { + tag: string; + style: string; + }; + + export type TagsParams = { + tagsKeyName: string; + styles: Styles; + defaultStyle: string; + styledTagsKeyName: string; + }; + + export type ConfigWithTagsStyles = { + tags?: TagsParams; + [key: string]: any; + }; + + export function CreateTagStyledEnhancerForConfig( + cfg: ConfigWithTagsStyles, + ): TagStyledEnhancer | null { + if (!cfg.tags) return null; + return new TagStyledEnhancer( + (issue: RedmineTypes.ExtendedIssue) => { + if ( + typeof issue[cfg.tags.tagsKeyName] === 'object' && + issue[cfg.tags.tagsKeyName].length > 0 + ) { + return issue[cfg.tags.tagsKeyName]; + } else { + return []; + } + }, + cfg.tags.styles, + cfg.tags.defaultStyle, + cfg.tags.styledTagsKeyName, + ); + } +} + +export class TagStyledEnhancer implements IssueEnhancerInterface { + private logger = new Logger(TagStyledEnhancer.name); + + name = 'tag-styled'; + + constructor( + private tagsGetter: (issue: RedmineTypes.ExtendedIssue) => string[], + private styles: TagStyledEnhancerNs.Styles, + private defaultStyle: string, + private keyName: string, + ) {} + + async enhance( + issue: RedmineTypes.ExtendedIssue, + ): Promise { + const tags = this.tagsGetter(issue); + this.logger.debug(`Found tags for issue_id = ${issue.id} - ${tags}`); + const styles = [] as TagStyledEnhancerNs.StyledTag[]; + for (let i = 0; i < tags.length; i++) { + const tagName = tags[i]; + if (this.styles[tagName]) { + styles.push({ + tag: tagName, + style: this.styles[tagName], + }); + } else { + styles.push({ + tag: tagName, + style: this.defaultStyle, + }); + } + } + issue[this.keyName] = styles; + return issue; + } +} diff --git a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts index 490a127..173f447 100644 --- a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts +++ b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-namespace */ import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer'; +import { TagStyledEnhancerNs } from '@app/event-emitter/issue-enhancers/tag-styled-enhancer'; import { TimePassedHighlightEnhancer } from '@app/event-emitter/issue-enhancers/time-passed-highlight-enhancer'; import { IssuesService, @@ -20,7 +21,7 @@ export namespace ListIssuesByUsersLikeJiraWidgetNs { userKeys: string[]; userSort?: boolean; statuses: string[]; - }; + } & TagStyledEnhancerNs.ConfigWithTagsStyles; } } @@ -56,6 +57,11 @@ export class ListIssuesByUsersLikeJiraWidgetService this.logger.error(errMsg); throw new Error(errMsg); } + await store.enhanceIssues([ + this.timePassedHighlightEnhancer, + this.issueUrlEnhancer, + TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams), + ]); const grouped = store.groupByStatusWithExtraToMultipleStories((issue) => { const users = [] as string[]; for (let i = 0; i < widgetParams.userKeys.length; i++) { @@ -92,10 +98,6 @@ export class ListIssuesByUsersLikeJiraWidgetService const rootIssue = await this.issuesService.getIssue(issueId); treeStore.setRootIssue(rootIssue); await treeStore.fillData(this.issuesLoader); - await treeStore.enhanceIssues([ - this.timePassedHighlightEnhancer, - this.issueUrlEnhancer, - ]); return treeStore.getFlatStore(); } @@ -108,10 +110,6 @@ export class ListIssuesByUsersLikeJiraWidgetService const issue = rawData[i]; store.push(issue); } - await store.enhanceIssues([ - this.timePassedHighlightEnhancer, - this.issueUrlEnhancer, - ]); return store; } diff --git a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts index 7cb6202..9d3fefe 100644 --- a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts +++ b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-namespace */ import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer'; +import { TagStyledEnhancerNs } from '@app/event-emitter/issue-enhancers/tag-styled-enhancer'; import { TimePassedHighlightEnhancer } from '@app/event-emitter/issue-enhancers/time-passed-highlight-enhancer'; import { IssuesService, @@ -21,7 +22,7 @@ export namespace ListIssuesByUsersWidgetNs { userKey: string; userSort?: boolean; statuses: string[]; - }; + } & TagStyledEnhancerNs.ConfigWithTagsStyles; export type FindResult = { result?: any; @@ -68,6 +69,11 @@ export class ListIssuesByUsersWidgetService this.logger.error(errMsg); throw new Error(errMsg); } + await store.enhanceIssues([ + this.timePassedHighlightEnhancer, + this.issueUrlEnhancer, + TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams), + ]); const grouped = store.groupByStatusWithExtra((issue) => { const res = this.getUserValueByKey(issue, widgetParams.userKey); return res.result || 'Unknown'; @@ -95,10 +101,6 @@ export class ListIssuesByUsersWidgetService const rootIssue = await this.issuesService.getIssue(issueId); treeStore.setRootIssue(rootIssue); await treeStore.fillData(this.issuesLoader); - await treeStore.enhanceIssues([ - this.timePassedHighlightEnhancer, - this.issueUrlEnhancer, - ]); return treeStore.getFlatStore(); } @@ -111,10 +113,6 @@ export class ListIssuesByUsersWidgetService const issue = rawData[i]; store.push(issue); } - await store.enhanceIssues([ - this.timePassedHighlightEnhancer, - this.issueUrlEnhancer, - ]); return store; } diff --git a/libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts b/libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts index 258e52e..a28f7ac 100644 --- a/libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts +++ b/libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-namespace */ import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer'; +import { TagStyledEnhancerNs } from '@app/event-emitter/issue-enhancers/tag-styled-enhancer'; import { TimePassedHighlightEnhancer } from '@app/event-emitter/issue-enhancers/time-passed-highlight-enhancer'; import { IssuesService, @@ -19,7 +20,7 @@ export namespace RootIssueSubTreesWidgetNs { parentsAsGroups?: boolean; groups?: GroupCfg; statuses: string[]; - }; + } & TagStyledEnhancerNs.ConfigWithTagsStyles; export type GroupCfg = { fromIssues: Group[]; @@ -64,6 +65,7 @@ export class RootIssueSubTreesWidgetService await treeStore.enhanceIssues([ this.timePassedHighlightEnhancer, this.issueUrlEnhancer, + TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams), ]); let stories: TreeIssuesStoreNs.Models.GetFlatStories.Result; if (widgetParams.parentsAsGroups) { diff --git a/libs/event-emitter/src/utils/flat-issues-store.ts b/libs/event-emitter/src/utils/flat-issues-store.ts index 21483ca..dd0d7f9 100644 --- a/libs/event-emitter/src/utils/flat-issues-store.ts +++ b/libs/event-emitter/src/utils/flat-issues-store.ts @@ -75,12 +75,15 @@ export class FlatIssuesStore { return; } - async enhanceIssues(enhancers: IssueEnhancerInterface[]): Promise { + async enhanceIssues( + enhancers: (IssueEnhancerInterface | null)[], + ): Promise { for (const issueId in this.issues) { if (Object.prototype.hasOwnProperty.call(this.issues, issueId)) { let issue = this.issues[issueId]; for (let i = 0; i < enhancers.length; i++) { const enhancer = enhancers[i]; + if (!enhancer) continue; issue = await enhancer.enhance(issue); this.issues[issueId] = issue; } diff --git a/src/issue-enhancers/custom-fields-enhancer.ts b/src/issue-enhancers/custom-fields-enhancer.ts index ac50d81..1694800 100644 --- a/src/issue-enhancers/custom-fields-enhancer.ts +++ b/src/issue-enhancers/custom-fields-enhancer.ts @@ -50,7 +50,10 @@ export class CustomFieldsEnhancer implements IssueEnhancerInterface { const tags = customFields.find((cf) => cf.name === 'Tags'); if (tags && tags.value) { - res.tags = tags.value.split(/[ ,;]/); + res.tags = tags.value + .split(/[ ,;]/) + .map((s) => s.trim()) + .filter((s) => !!s); } const sp = customFields.find( diff --git a/views/simple-kanban-board.hbs b/views/simple-kanban-board.hbs index 93f7ccd..c26e710 100644 --- a/views/simple-kanban-board.hbs +++ b/views/simple-kanban-board.hbs @@ -62,6 +62,11 @@ .timepassed-dot.cold { background-color: rgba(0, 0, 255, 0.1); } + .kanban-card-tag { + font-size: 8pt; + border-radius: 4px; + padding: 2px; + } @@ -78,6 +83,14 @@
Исп.: {{this.current_user.name}}
Прогресс: {{this.done_ration}}
Трудозатраты: {{this.total_spent_hours}} / {{this.total_estimated_hours}}
+ {{#if this.styledTags}} +
+ Tags: + {{#each this.styledTags}} + {{this.tag}} + {{/each}} +
+ {{/if}} {{/each}}