Вывод тегов на карточках задач в kanban-досках
This commit is contained in:
parent
81966907ac
commit
4f0355c09c
7 changed files with 125 additions and 21 deletions
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer';
|
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 { TimePassedHighlightEnhancer } from '@app/event-emitter/issue-enhancers/time-passed-highlight-enhancer';
|
||||||
import {
|
import {
|
||||||
IssuesService,
|
IssuesService,
|
||||||
|
|
@ -20,7 +21,7 @@ export namespace ListIssuesByUsersLikeJiraWidgetNs {
|
||||||
userKeys: string[];
|
userKeys: string[];
|
||||||
userSort?: boolean;
|
userSort?: boolean;
|
||||||
statuses: string[];
|
statuses: string[];
|
||||||
};
|
} & TagStyledEnhancerNs.ConfigWithTagsStyles;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,6 +57,11 @@ export class ListIssuesByUsersLikeJiraWidgetService
|
||||||
this.logger.error(errMsg);
|
this.logger.error(errMsg);
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
|
await store.enhanceIssues([
|
||||||
|
this.timePassedHighlightEnhancer,
|
||||||
|
this.issueUrlEnhancer,
|
||||||
|
TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams),
|
||||||
|
]);
|
||||||
const grouped = store.groupByStatusWithExtraToMultipleStories((issue) => {
|
const grouped = store.groupByStatusWithExtraToMultipleStories((issue) => {
|
||||||
const users = [] as string[];
|
const users = [] as string[];
|
||||||
for (let i = 0; i < widgetParams.userKeys.length; i++) {
|
for (let i = 0; i < widgetParams.userKeys.length; i++) {
|
||||||
|
|
@ -92,10 +98,6 @@ export class ListIssuesByUsersLikeJiraWidgetService
|
||||||
const rootIssue = await this.issuesService.getIssue(issueId);
|
const rootIssue = await this.issuesService.getIssue(issueId);
|
||||||
treeStore.setRootIssue(rootIssue);
|
treeStore.setRootIssue(rootIssue);
|
||||||
await treeStore.fillData(this.issuesLoader);
|
await treeStore.fillData(this.issuesLoader);
|
||||||
await treeStore.enhanceIssues([
|
|
||||||
this.timePassedHighlightEnhancer,
|
|
||||||
this.issueUrlEnhancer,
|
|
||||||
]);
|
|
||||||
return treeStore.getFlatStore();
|
return treeStore.getFlatStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,10 +110,6 @@ export class ListIssuesByUsersLikeJiraWidgetService
|
||||||
const issue = rawData[i];
|
const issue = rawData[i];
|
||||||
store.push(issue);
|
store.push(issue);
|
||||||
}
|
}
|
||||||
await store.enhanceIssues([
|
|
||||||
this.timePassedHighlightEnhancer,
|
|
||||||
this.issueUrlEnhancer,
|
|
||||||
]);
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer';
|
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 { TimePassedHighlightEnhancer } from '@app/event-emitter/issue-enhancers/time-passed-highlight-enhancer';
|
||||||
import {
|
import {
|
||||||
IssuesService,
|
IssuesService,
|
||||||
|
|
@ -21,7 +22,7 @@ export namespace ListIssuesByUsersWidgetNs {
|
||||||
userKey: string;
|
userKey: string;
|
||||||
userSort?: boolean;
|
userSort?: boolean;
|
||||||
statuses: string[];
|
statuses: string[];
|
||||||
};
|
} & TagStyledEnhancerNs.ConfigWithTagsStyles;
|
||||||
|
|
||||||
export type FindResult = {
|
export type FindResult = {
|
||||||
result?: any;
|
result?: any;
|
||||||
|
|
@ -68,6 +69,11 @@ export class ListIssuesByUsersWidgetService
|
||||||
this.logger.error(errMsg);
|
this.logger.error(errMsg);
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
|
await store.enhanceIssues([
|
||||||
|
this.timePassedHighlightEnhancer,
|
||||||
|
this.issueUrlEnhancer,
|
||||||
|
TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams),
|
||||||
|
]);
|
||||||
const grouped = store.groupByStatusWithExtra((issue) => {
|
const grouped = store.groupByStatusWithExtra((issue) => {
|
||||||
const res = this.getUserValueByKey(issue, widgetParams.userKey);
|
const res = this.getUserValueByKey(issue, widgetParams.userKey);
|
||||||
return res.result || 'Unknown';
|
return res.result || 'Unknown';
|
||||||
|
|
@ -95,10 +101,6 @@ export class ListIssuesByUsersWidgetService
|
||||||
const rootIssue = await this.issuesService.getIssue(issueId);
|
const rootIssue = await this.issuesService.getIssue(issueId);
|
||||||
treeStore.setRootIssue(rootIssue);
|
treeStore.setRootIssue(rootIssue);
|
||||||
await treeStore.fillData(this.issuesLoader);
|
await treeStore.fillData(this.issuesLoader);
|
||||||
await treeStore.enhanceIssues([
|
|
||||||
this.timePassedHighlightEnhancer,
|
|
||||||
this.issueUrlEnhancer,
|
|
||||||
]);
|
|
||||||
return treeStore.getFlatStore();
|
return treeStore.getFlatStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,10 +113,6 @@ export class ListIssuesByUsersWidgetService
|
||||||
const issue = rawData[i];
|
const issue = rawData[i];
|
||||||
store.push(issue);
|
store.push(issue);
|
||||||
}
|
}
|
||||||
await store.enhanceIssues([
|
|
||||||
this.timePassedHighlightEnhancer,
|
|
||||||
this.issueUrlEnhancer,
|
|
||||||
]);
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
import { IssueUrlEnhancer } from '@app/event-emitter/issue-enhancers/issue-url-enhancer';
|
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 { TimePassedHighlightEnhancer } from '@app/event-emitter/issue-enhancers/time-passed-highlight-enhancer';
|
||||||
import {
|
import {
|
||||||
IssuesService,
|
IssuesService,
|
||||||
|
|
@ -19,7 +20,7 @@ export namespace RootIssueSubTreesWidgetNs {
|
||||||
parentsAsGroups?: boolean;
|
parentsAsGroups?: boolean;
|
||||||
groups?: GroupCfg;
|
groups?: GroupCfg;
|
||||||
statuses: string[];
|
statuses: string[];
|
||||||
};
|
} & TagStyledEnhancerNs.ConfigWithTagsStyles;
|
||||||
|
|
||||||
export type GroupCfg = {
|
export type GroupCfg = {
|
||||||
fromIssues: Group[];
|
fromIssues: Group[];
|
||||||
|
|
@ -64,6 +65,7 @@ export class RootIssueSubTreesWidgetService
|
||||||
await treeStore.enhanceIssues([
|
await treeStore.enhanceIssues([
|
||||||
this.timePassedHighlightEnhancer,
|
this.timePassedHighlightEnhancer,
|
||||||
this.issueUrlEnhancer,
|
this.issueUrlEnhancer,
|
||||||
|
TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams),
|
||||||
]);
|
]);
|
||||||
let stories: TreeIssuesStoreNs.Models.GetFlatStories.Result;
|
let stories: TreeIssuesStoreNs.Models.GetFlatStories.Result;
|
||||||
if (widgetParams.parentsAsGroups) {
|
if (widgetParams.parentsAsGroups) {
|
||||||
|
|
|
||||||
|
|
@ -75,12 +75,15 @@ export class FlatIssuesStore {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async enhanceIssues(enhancers: IssueEnhancerInterface[]): Promise<void> {
|
async enhanceIssues(
|
||||||
|
enhancers: (IssueEnhancerInterface | null)[],
|
||||||
|
): Promise<void> {
|
||||||
for (const issueId in this.issues) {
|
for (const issueId in this.issues) {
|
||||||
if (Object.prototype.hasOwnProperty.call(this.issues, issueId)) {
|
if (Object.prototype.hasOwnProperty.call(this.issues, issueId)) {
|
||||||
let issue = this.issues[issueId];
|
let issue = this.issues[issueId];
|
||||||
for (let i = 0; i < enhancers.length; i++) {
|
for (let i = 0; i < enhancers.length; i++) {
|
||||||
const enhancer = enhancers[i];
|
const enhancer = enhancers[i];
|
||||||
|
if (!enhancer) continue;
|
||||||
issue = await enhancer.enhance(issue);
|
issue = await enhancer.enhance(issue);
|
||||||
this.issues[issueId] = issue;
|
this.issues[issueId] = issue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,10 @@ export class CustomFieldsEnhancer implements IssueEnhancerInterface {
|
||||||
|
|
||||||
const tags = customFields.find((cf) => cf.name === 'Tags');
|
const tags = customFields.find((cf) => cf.name === 'Tags');
|
||||||
if (tags && tags.value) {
|
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(
|
const sp = customFields.find(
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,11 @@
|
||||||
.timepassed-dot.cold {
|
.timepassed-dot.cold {
|
||||||
background-color: rgba(0, 0, 255, 0.1);
|
background-color: rgba(0, 0, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
.kanban-card-tag {
|
||||||
|
font-size: 8pt;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
@ -78,6 +83,14 @@
|
||||||
<div>Исп.: {{this.current_user.name}}</div>
|
<div>Исп.: {{this.current_user.name}}</div>
|
||||||
<div>Прогресс: {{this.done_ration}}</div>
|
<div>Прогресс: {{this.done_ration}}</div>
|
||||||
<div>Трудозатраты: {{this.total_spent_hours}} / {{this.total_estimated_hours}}</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>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue