轻松编程:在一个工作日内为GitLab提供看板

星期一的工作开始于以下对话:

负责人(P):您的团队中尚不清楚谁在做什么。
I(I):是的,我们没有一种工具可以反映任务工作的总体情况。 Hitlab中有看板板,但它们仅在项目和组中使用。 一个普通的看板可以解决这个问题。
R:然后做个木板。
我:到早上就可以了。

初学者团队领导者一早意识到自己的团队需要看板,这是迟早的事情。 它消除了对开发过程控制的忧虑,并对未来充满信心。 通常,解决这个问题的方法是:最低限度地要求经理购买磁性板,一套彩色贴纸和几个用于该板的标记。 好吧,或者使用诸如Jira之类的服务。 但是我们的团队有一个远程开发人员,一个gitlab处于封闭状态(出于信息安全的原因,Internet上不可用),因此我不得不选择两种可能的解决方案中最简单的一种:

a)创建一个机械臂及其控制器,该机械臂和控制器可以将贴纸远程重新粘贴在板上,而为了不使决定变得复杂,我们将不得不为无法进入的同事写下贴纸,这是不公平的;
b)实现了一个看板软件,可以从我们的Hitlab收集所有任务。

当然,灵魂靠在物理板的管上。 我什至开始考虑将DualShock 4用作机械手臂控制器,但我本人指定了第二天早上的截止日期,所以我不得不放弃一个毫无生气的软件解决方案。


在开始介绍该故事的技术部分之前,我将向您介绍我们如何在Onlanta中使用gitlab。 Hitlab中的组对应于内部/外部客户或一个单独的大型项目,并且该项目是特定服务代码的存储库。 例如,我们有一个计费控制面板组,其中有四个项目是服务和Web应用程序,还有一个营销组,其中有一个公司网站的项目,一个用于与amoCRM集成的微服务,各种登录页面等。 当前,我们有9个活动组,并且程序员人数较少,因此所有程序员都参与了所有组。

我们所需的只是一个页面,其中包含来自hitlab,所有组和项目的所有任务,但是不幸的是,GitLab中没有此类功能。 快速谷歌没有帮助找到一个独立的解决方案,因此

我们将需要:


-Node.js后端-用于使用GitLab收集任务并将其传输到客户端;
-Vue.js上的客户端-快速,美观地进行操作;
-当然要用TypeScript来调味!
-PostgreSQL-我们将在其中存储有关任务及其在董事会中位置的信息;
-8小时的工作时间;
-好心情

最重要的条件是开发应该既简单又有趣,我们称之为“简单编程”。

动物园


出于明显的原因,我无法使用公司Hitlab的项目和任务来说明有关Habré的文章,因此我将Hitlab与猴子和鳄鱼一起部署。 是的,这是一个虚构动物园的gitlab,但是在那里设置的任务不会比其他任何gitlab都要严重。 看一看项目列表:


卡尔卡斯


经过多年的发展,我对如何构建Node.js的后端应用程序形成了自己的见解,这种方法采用了框架的形式-一组文件。 为了创建一个新项目,我只复制了带有模板的目录。 这不可能永远持续下去,我制作了karcass npm软件包( carcass这个名字已经和我一样被同一位自行车爱好者所采用了),它使创建应用程序基础的过程自动化:


现在,我们需要处理组,项目,任务,当然还有表演者的接收和存储。

类型ORM


“我看到了一些实体……”
-来自心理学论坛的讨论

由于我们需要以对我们方便的形式呈现gitlab中的数据,因此在收集和呈现数据之间的过程中,我们需要以某种方式进行存储。 最近,我发现了一个很棒的库,用于处理数据库TypeORM,它对于JS生态系统来说是很棒的。 它仍然是绿色的,但是很有可能将Sequelize从王位上赶下。

在几十分钟内,将出现用户项目任务的迁移和类。 这是其中之一:

import { Entity, PrimaryColumn, Column } from 'typeorm' @Entity({ name: 'group' }) export class Group { @PrimaryColumn('integer') public id!: number @Column('varchar') public name!: string @Column('varchar') public url!: string } 

为了创建迁移,我再次制作了CreateMigrationCommand助手自行车 。 此命令的使用非常简单:


在Issue类中,除了存储来自Gitlab的数据的字段外,我们还添加了一些字段,由于这些字段引起了大惊小怪,也就是说,指示了任务在看板上的位置:

 export class Issue extends AbstractEntity { /* ... */ @Column('varchar', { name: 'kanban_status' }) public kanbanStatus?: 'new'|'planed'|'working'|'checking'|'done' @Column('int', { name: 'kanban_order' }) public kanbanOrder?: number } 

我们需要信息


当我刚开始接触GitLab时,对我来说这似乎是一个简单(讨厌)的产品,但是每天使用它,我意识到自己错了。 所以这一次让我感到惊讶-事实证明,GitLab具有非常丰富的API,几乎涵盖了通过用户界面可用的所有功能。 但是要解决我们的问题,您仅需要四种方法,它们的名称就可以说明一切: / users/ groups/ projects/ projects /:id / issues 。 GitlabService将负责与GitLab的直接交互,其他类将访问它。 例如,GroupService类的updateGroups方法的实现如下所示:

 export class GroupService extends AbstractService { /* ... */ public async updateGroups() { for (const data of await this.app.gitlabService.getGroups()) { // <=     GitlabService let group = await this.getGroup(data.id) if (!group) { group = this.groupRepository.create({ id: data.id, }) } group.name = data.name group.url = data.web_url await this.groupRepository.save(group) } } /* ... */ } 

updateProjectsCommand命令将负责从gitlab获取信息并更新数据库中的相关信息的操作,可以从控制台调用它,但是我们的应用程序将以config中指定的频率自行启动它:

 export class Application { /* ... */ protected initCron() { if (this.config.gitlab.updateInterval) { setInterval(async () => { if (!this.updateProjectsCommand) { this.updateProjectsCommand = new UpdateProjectsCommand(this) } await this.updateProjectsCommand.execute() }, this.config.gitlab.updateInterval * 1000) } } /* ... */ } 

我们不会在面板上硬编码列的编号,颜色和名称,而是使它们可在config.js中自定义。 我们有五个,但是对于某人来说,可能只需要两个,并且颜色是酸性的:

 columns: [ { key: 'new', title: '', color: 'rgb(255, 255, 219)' }, { key: 'planed', title: '', color: 'rgb(236, 236, 191)' }, { key: 'working', title: ' ', color: 'rgb(253, 214, 162)' }, { key: 'checking', title: ' ', color: 'rgb(162, 226, 253)' }, { key: 'done', title: '', color: 'rgb(162, 253, 200)' }, ], 

最主要的是应该至少有两个,否则应用程序将无法启动。

为了与前台互动,我们需要一种从数据库接收任务并将其分类为“列”的方法:

显示IssueService.ts
 export class IssueService extends AbstractService { /* ... */ public async getKanban() { let issues = await this.issueRepository.find({ where: { updatedTimestamp: MoreThanOrEqual(new Date().getTimestamp() - 60 * 60 * 24 * 30) }, order: { kanbanOrder: 'ASC', updatedTimestamp: 'DESC' }, }) const keys = this.app.config.columns.map(c => c.key) const result: { [key: string]: Issue[] } = {} for (let ki = keys.length - 1; ki >= 0; ki--) { const key = keys[ki] if (ki === keys.length - 1) { //        result[key] = issues.filter(i => i.closed) } else if (ki === 0) { // ,  ,    ,    result[key] = issues } else { result[key] = issues.filter(i => i.kanbanStatus === key) } issues = issues.filter(i => !result[key].includes(i)) //     } return result } /* ... */ } 

还有一个保存新任务位置的控制器:

显示IssueController.ts
 export default class IssueController extends AbstractController { /* ... */ public async kanbanUpdate(data: IQueryData) { const keys = this.app.config.columns.map(c => c.key) for (const c of data.params as { key: string, title: string, color: string, issues: number[] }[]) { if (keys.indexOf(c.key) < 0) { continue } let index = 0 for (const i of c.issues) { index++ const issue = await this.app.issueService.getIssue(i) if (!issue) { continue } issue.kanbanStatus = c.key issue.kanbanOrder = index this.app.issueService.issueRepository.save(issue) } } } } 

斯托什。 我们从GitLab收集信息,准备显示板的数据,甚至知道如何保存板上任务的更新位置。 剩下的唯一事情就是制作电路板本身。 在这里,我们拥有一个强大的工具:

Vue CLI-而不是千言万语



多伦多证券交易所


为了便于重构,发现错误,掉线和其他使人省心的方法,我们使用Vue.js 几乎开箱即用*的 tsx模板。 例如,董事会中最重要的组件,即任务演示的组件:

显示代码Issue.tsx
 import { Vue, Component, Prop, Watch } from 'vue-property-decorator' import './Issue.css' import { CreateElement, VNode } from 'vue' interface IIssue { groupId: number groupName: string groupUrl: string projectName: string projectUrl: string url: string title: string executor: { color: string, name: string } spent: number estimate: number } @Component export default class extends Vue { @Prop() public issue!: IIssue public issueValue!: IIssue public selectUser = false @Watch('issue', { immediate: true }) public onIssueChange() { this.issueValue = this.issue } // eslint-disable-next-line @typescript-eslint/no-unused-vars public render(h: CreateElement): VNode { return <div class="kvcIssue"> <div class="kvciTitle"> { this.issueValue.groupId ? <small><a href={ this.issueValue.groupUrl } target="_blank"> { this.issueValue.groupName } </a> / </small> : undefined } <a href={ this.issueValue.projectUrl } target="_blank">{ this.issueValue.projectName }</a> </div> <div class="kvciText"><a href={ this.issueValue.url } target="_blank">{ this.issueValue.title }</a></div> <div class={ ['kvciExecutor', this.issueValue.executor ? '' : 'none'] }> <span class="timeTd"> <span title=" ">{ this.issueValue.spent.time() }</span> / <span title=" "> { this.issueValue.estimate.time() }</span> </span> { this.issueValue.executor ? <span class="kvcieUser" style={ { background: this.issueValue.executor.color } }> { this.issueValue.executor.name } </span> : <span class="kvcieSelect"> </span> } </div> </div> } } 

该组件显示任务栏。 请注意,该任务实际上已经花费并计划了时间:


也有Column.tsxKanban.tsx组件 。 只有三个组件可以提供板上的任务:


Vue.Draggable的腿长于SortableJS ,负责在板上移动任务。 使用非常简单:

 import Draggable from 'vuedraggable' /* ... */ export default class extends Vue { /* ... */ public render(h: CreateElement): VNode { /* ... */ <Draggable class="kvcIssues" vModel={ this.issuesValue } group={ { name: 'issues', pull: true, put: true } } onEnd={ this.onDrag } onAdd={ this.onDrag } > { this.issuesValue.map(i => <Issue key={ i.id } issue={ i } />) } </Draggable> /* ... */ } } 

您肯定会注意到每个用户都有自己的颜色,该颜色用于显示登录名。 这非常方便-在许多任务中,您可以快速找到自己的任务。 有两种方法可以实现“多色性”:为每个用户手动设置颜色或基于某种颜色生成颜色。

给我你的用户名,我会告诉你你有什么颜色


User类的颜色获取器负责颜色生成。 我特别在后端实现了此功能,因为我需要加密,而将整个库拖到最前端执行这样的小任务是一种犯罪。 而且,由后方而不是前方负责实体的特征更为正确:

 export class User extends AbstractEntity { /* ... */ public get color() { const hash = crypto.createHash('md5').update(this.username).digest() //  md5       .     -   . const result: number[] = [] for (let i = 0; i < 3; i++) { result.push(Math.round(200 - hash[i] / 255 * 150)) //      ,     ,       } return `rgb(${result.join(', ')})` } } 

对于那些想知道将要使用哪种颜色的人,我并不是很懒于在CodePen上剪切代码段

就这样


工作周二开始于以下对话:

R:恩,董事会准备好了吗?
我:是的,在这里。
R:哦,太酷了!

我的“工作日”的结果在这里: https : //github.com/onlanta/kanban 。 您不仅可以看到,触摸它,还可以使用它(那里的说明)。 您可以在此基础上为Hitlab建立一个全时跟踪器,这是我们在公司中所做的。

我为什么决定写这篇笔记? 这是我职业生涯中最近的光辉时刻之一,当时的任务很重要,很有趣,同时又很简单,其解决方案又简单又优雅。 我建议在评论中回顾您练习中的类似任务。

Source: https://habr.com/ru/post/zh-CN462855/


All Articles