سهولة البرمجة: Kanban board for GitLab في يوم عمل واحد

بدأ العمل يوم الاثنين بالحوار التالي:

القائد (ع): ليس واضحًا في فريقك من يفعل ماذا.
أنا (الأول): نعم ، ليس لدينا أداة من شأنها أن تعكس الصورة العامة للعمل في المهام. هناك مجالس kanban في hitlab ، لكنها فقط في سياق المشاريع والمجموعات. ومن شأن لوحة kanban المشتركة حل المشكلة.
R: ثم اصنع لوحة.
لي: بحلول الصباح سيكون جاهزاً.

عاجلاً أم آجلاً ، تأتي اللحظة في حياة قائد فريق مبتدئ عندما يدرك أن فريقه يحتاج إلى لوحة kanban. يزيل القلق حول التحكم في عملية التطوير ويعطي الثقة في المستقبل. عادة ما يتم حل هذه المشكلة عن طريق أدنى طلب إلى المدير لشراء لوحة مغناطيسية ومجموعة من الملصقات متعددة الألوان واثنين من علامات اللوحة. حسنا ، أو باستخدام خدمات مثل جيرا. لكن لدى فريقنا مطور واحد على جهاز التحكم عن بُعد ، وجيتلاب في دائرة مغلقة (غير متوفرة من الإنترنت لأسباب تتعلق بأمان المعلومات) ، لذلك كان علي اختيار أبسط حلين ممكنين:

أ) إنشاء ذراع ميكانيكية ووحدة تحكم له ، والتي ستعيد لصق الملصقات على اللوحة عن بُعد ، وفي حين لا نعقد القرار ، يتعين علينا أن نكتب على الملصقات لزميلنا الذي يتعذر الوصول إليه ، وهو أمر غير عادل ؛
ب) تنفيذ لوحة kanban البرمجية التي من شأنها أن تجمع جميع المهام من hitlab لدينا.

بالطبع ، تكمن الروح ضد أنبوب اللوحة المادية. حتى أنني بدأت أفكر في استخدام DualShock 4 كوحدة تحكم ذراع ميكانيكية ، لكنني قمت بتحديد الموعد النهائي في صباح اليوم التالي ، لذلك اضطررت إلى الحصول على حل برمجي لا مبرر له.


قبل البدء في وصف الجزء الفني من القصة ، سوف أخبرك عن كيفية استخدامنا لجيتلاب في أونلانتا. المجموعات في hitlab التي نتقابلها مع العميل الداخلي / الخارجي أو مشروع كبير منفصل ، والمشروع هو مستودع لرمز خدمة معينة. على سبيل المثال ، لدينا مجموعة من لوحة تحكم الفوترة تضم أربعة مشاريع خدمات وتطبيقات على شبكة الإنترنت ، وهناك مجموعة تسويقية يوجد فيها مشروع لموقع ويب للشركة ، وخدمة microservice للتكامل مع amoCRM ، وجميع أنواع الصفحات المقصودة وما إلى ذلك. لدينا حاليًا تسع مجموعات نشطة ، وهناك عدد أقل من المبرمجين ، بحيث يشارك جميع المبرمجين في جميع المجموعات.

وكل ما نحتاجه هو صفحة بها جميع المهام من hitlab ، من جميع المجموعات والمشاريع ، لكن هذه الوظيفة في GitLab ، للأسف ، ليست كذلك. Quick google لم يساعد في إيجاد حل مستقل ، لذلك

سنحتاج:


- الخلفية على Node.js - لجمع المهام مع GitLab ونقلها إلى العميل ؛
- عميل على Vue.js - للقيام بذلك بسرعة وبشكل جميل ؛
- الموسم كل شيء مع TypeScript ، بالطبع!
- PostgreSQL - حيث سنقوم بتخزين المعلومات حول المهام وموقعها على السبورة ؛
- 8 ساعات من وقت العمل ؛
- مزاج جيد.

الشرط الأكثر أهمية هو أن التنمية يجب أن تكون سهلة وممتعة ، دعنا نسميها "برمجة سهلة".

حديقة الحيوان


لأسباب واضحة ، لا يمكنني استخدام المشاريع والمهام من hitlab للشركات الخاصة بنا لتوضيح المقال الخاص بـ Habré ، لذلك قمت بنشر hitlab ، مع القرود والتماسيح. نعم ، هذا gitlab لحديقة حيوانات خيالية ، ولكن لن يتم تعيين مهام أقل خطورة من أي gitlab الأخرى. ألقِ نظرة على قائمة المشاريع:


karcass


على مدار سنوات التطوير ، قمت بتطوير رؤيتي الخاصة حول كيفية إنشاء تطبيق الواجهة الخلفية على Node.js ، واتخذ هذا النهج شكل إطار عمل - مجموعة من الملفات. لإنشاء مشروع جديد ، قمت فقط بنسخ الدليل باستخدام القالب. هذا لا يمكن أن يستمر إلى الأبد ، وقمت بعمل حزمة كاركاس npm (اسم الذبيحة تم أخذها بالفعل من قبل نفس المتحمسين للدراجات كما فعلت) ، والتي تعمل تلقائيًا على إنشاء أساس للتطبيق:


نحن الآن بحاجة إلى التعامل مع استلام وتخزين المجموعات ، والمشاريع ، والمهام ، وبطبيعة الحال ، فناني الأداء.

TypeORM


"أرى بعض الكيانات ..."
- من المناقشات في منتدى الوسطاء

نظرًا لأننا نحتاج إلى تقديم البيانات من 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 ، نضيف الحقول التي بدأت بسببها الضجة بأكملها ، أي تشير إلى موقع المهمة على لوحة kanban:

 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 لديها واجهة برمجة تطبيقات غنية للغاية تغطي تقريبا جميع الوظائف المتاحة من خلال واجهة المستخدم. ولكن لحل مشكلتنا ، ستحتاج إلى أربع طرق فقط ، تتحدث أسماء عن نفسها: / المستخدمين ، / المجموعات ، / المشاريع ، / المشاريع /: الهوية / القضايا . ستكون GitlabService مسؤولة عن التفاعل المباشر مع GitLab ، وسوف تصل إليه الفئات الأخرى. على سبيل المثال ، يبدو تطبيق أسلوب updateGroups لفئة GroupService كما يلي:

 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 وتحديث المعلومات ذات الصلة في قاعدة البيانات الخاصة بنا ، والتي يمكن استدعاؤها من وحدة التحكم ، ولكن تطبيقنا سيطلقها بنفسه بالتردد المحدد في التكوين:

 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) } } } } 

Shtosh. نقوم بجمع المعلومات من GitLab ، وإعداد البيانات لعرض اللوحة ، وحتى معرفة كيفية حفظ وظائف المهام المحدثة على اللوحة. الشيء الوحيد المتبقي هو جعل المجلس نفسه. وهنا نحن مسلحون بأداة قوية:

Vue CLI - بدلاً من ألف كلمة



TSX


لتسهيل إعادة التوطين ، والعثور على الأخطاء ، واللينت وغيرها من راحة البال ، نستخدم قوالب tsx التي يدعمها Vue.js خارج الصندوق * تقريبًا . على سبيل المثال ، أهم عنصر في مجلسنا ، مكون عرض المهمة:

إظهار الكود 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.tsx و Kanban.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> /* ... */ } } 

بالتأكيد لاحظت أن لكل مستخدم لونه الخاص ، والذي يستخدم لعرض تسجيل الدخول. هذا مريح للغاية - من بين مجموعة من المهام ، يمكنك العثور على مهامك بسرعة. هناك طريقتان لتحقيق "متعدد الألوان": لضبط اللون بأيدي لكل مستخدم أو لإنشاءه استنادًا إلى شيء ما.

أعطني اسم المستخدم الخاص بك وسأخبرك عن اللون الذي لديك


حاصل اللون من فئة المستخدم هو المسؤول عن توليد الألوان. لقد قمت على وجه التحديد بتطبيق هذه الوظيفة على الواجهة الخلفية ، لأنني كنت بحاجة إلى تشفير ، ومن جرم سحب مكتبة بأكملها إلى المقدمة للقيام بهذه المهمة الصغيرة. ويكون الأمر أكثر صحة عندما يكون الظهر ، وليس الجبهة ، مسؤولاً عن خصائص الكيانات:

 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/ar462855/


All Articles