Вывод тегов на карточках задач в kanban-досках

This commit is contained in:
Pavel Gnedov 2023-02-15 10:17:36 +07:00
parent 81966907ac
commit 4f0355c09c
7 changed files with 125 additions and 21 deletions

View file

@ -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<string, string>;
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<RedmineTypes.ExtendedIssue> {
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;
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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) {

View file

@ -75,12 +75,15 @@ export class FlatIssuesStore {
return;
}
async enhanceIssues(enhancers: IssueEnhancerInterface[]): Promise<void> {
async enhanceIssues(
enhancers: (IssueEnhancerInterface | null)[],
): Promise<void> {
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;
}

View file

@ -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(

View file

@ -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;
}
</style>
</head>
@ -78,6 +83,14 @@
<div>Исп.: {{this.current_user.name}}</div>
<div>Прогресс: {{this.done_ration}}</div>
<div>Трудозатраты: {{this.total_spent_hours}} / {{this.total_estimated_hours}}</div>
{{#if this.styledTags}}
<div>
Tags:
{{#each this.styledTags}}
<span class="kanban-card-tag" style="{{{this.style}}}">{{this.tag}}</span>
{{/each}}
</div>
{{/if}}
</div>
{{/each}}
</div>