-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Migrate Backlogs to JavaScript classes, type improvements #19907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,10 @@ | |||||
| // See COPYRIGHT and LICENSE files for more details. | ||||||
| //++ | ||||||
|
|
||||||
| import { Burndown } from './burndown'; | ||||||
| import { EditableSprint } from './sprint'; | ||||||
| import { EditableStory, EditableStoryType } from './story'; | ||||||
|
|
||||||
| /****************************************** | ||||||
| BACKLOG | ||||||
| A backlog is a visual representation of | ||||||
|
|
@@ -37,146 +41,137 @@ | |||||
| sheet (or in Redmine Backlogs!) to | ||||||
| visualize the sprint. | ||||||
| ******************************************/ | ||||||
|
|
||||||
| // @ts-expect-error TS(2304): Cannot find name 'RB'. | ||||||
| RB.Backlog = (function ($) { | ||||||
| // @ts-expect-error TS(2304): Cannot find name 'RB'. | ||||||
| return RB.Object.create({ | ||||||
|
|
||||||
| initialize(el:any) { | ||||||
| this.$ = $(el); | ||||||
| this.el = el; | ||||||
|
|
||||||
| // Associate this object with the element for later retrieval | ||||||
| this.$.data('this', this); | ||||||
|
|
||||||
| // Make the list sortable | ||||||
| this.getList().sortable({ | ||||||
| connectWith: '.stories', | ||||||
| dropOnEmpty: true, | ||||||
| start: this.dragStart, | ||||||
| stop: this.dragStop, | ||||||
| update: this.dragComplete, | ||||||
| receive: this.dragChanged, | ||||||
| remove: this.dragChanged, | ||||||
| containment: $('#backlogs_container'), | ||||||
| cancel: 'input, textarea, button, select, option, .prevent_drag', | ||||||
| scroll: true, | ||||||
| helper(event:any, ui:any) { | ||||||
| const $clone = $(ui).clone(); | ||||||
| $clone.css('position', 'absolute'); | ||||||
| return $clone.get(0); | ||||||
| }, | ||||||
| }); | ||||||
|
|
||||||
| // Observe menu items | ||||||
| this.$.find('.add_new_story').click(this.handleNewStoryClick); | ||||||
|
|
||||||
| if (this.isSprintBacklog()) { | ||||||
| // @ts-expect-error TS(2304): Cannot find name 'RB'. | ||||||
| RB.Factory.initialize(RB.Sprint, this.getSprint()); | ||||||
| // @ts-expect-error TS(2304): Cannot find name 'RB'. | ||||||
| this.burndown = RB.Factory.initialize(RB.Burndown, this.$.find('.show_burndown_chart')); | ||||||
| this.burndown.setSprintId(this.getSprint().data('this').getID()); | ||||||
| } | ||||||
|
|
||||||
| // Initialize each item in the backlog | ||||||
| this.getStories().each(function (this:any, index:any) { | ||||||
| // 'this' refers to an element with class="story" | ||||||
| // @ts-expect-error TS(2304): Cannot find name 'RB'. | ||||||
| RB.Factory.initialize(RB.Story, this); | ||||||
| }); | ||||||
|
|
||||||
| if (this.isSprintBacklog()) { | ||||||
| this.refresh(); | ||||||
| } | ||||||
| }, | ||||||
|
|
||||||
| dragChanged(e:any, ui:any) { | ||||||
| $(this).parents('.backlog').data('this').refresh(); | ||||||
| }, | ||||||
|
|
||||||
| dragComplete(e:any, ui:any) { | ||||||
| const isDropTarget = (ui.sender === null || ui.sender === undefined); | ||||||
|
|
||||||
| // jQuery triggers dragComplete of source and target. | ||||||
| // Thus we have to check here. Otherwise, the story | ||||||
| // would be saved twice. | ||||||
| if (isDropTarget) { | ||||||
| ui.item.data('this').saveDragResult(); | ||||||
| } | ||||||
| }, | ||||||
|
|
||||||
| dragStart(e:any, ui:any) { | ||||||
| ui.item.addClass('dragging'); | ||||||
| }, | ||||||
|
|
||||||
| dragStop(e:any, ui:any) { | ||||||
| ui.item.removeClass('dragging'); | ||||||
| }, | ||||||
|
|
||||||
| getSprint() { | ||||||
| return $(this.el).find('.model.sprint').first(); | ||||||
| }, | ||||||
|
|
||||||
| getStories() { | ||||||
| return this.getList().children('.story'); | ||||||
| }, | ||||||
|
|
||||||
| getList() { | ||||||
| return this.$.children('.stories').first(); | ||||||
| }, | ||||||
|
|
||||||
| handleNewStoryClick(e:any) { | ||||||
| const toggler = $(this).parents('.header').find('.toggler'); | ||||||
| if (toggler.hasClass('closed')) { | ||||||
| toggler.click(); | ||||||
| } | ||||||
| e.preventDefault(); | ||||||
| $(this).parents('.backlog').data('this').newStory(); | ||||||
| }, | ||||||
|
|
||||||
| // return true if backlog has an element with class="sprint" | ||||||
| isSprintBacklog() { | ||||||
| return $(this.el).find('.sprint').length === 1; | ||||||
| }, | ||||||
|
|
||||||
| newStory() { | ||||||
| let story; | ||||||
| let o; | ||||||
|
|
||||||
| story = $('#story_template').children().first().clone(); | ||||||
| this.getList().prepend(story); | ||||||
|
|
||||||
| // @ts-expect-error TS(2304): Cannot find name 'RB'. | ||||||
| o = RB.Factory.initialize(RB.Story, story[0]); | ||||||
| o.edit(); | ||||||
|
|
||||||
| story.find('.editor').first().focus(); | ||||||
| }, | ||||||
|
|
||||||
| refresh() { | ||||||
| this.recalcVelocity(); | ||||||
| this.recalcOddity(); | ||||||
| }, | ||||||
|
|
||||||
| recalcVelocity() { | ||||||
| let total:any; | ||||||
|
|
||||||
| if (!this.isSprintBacklog()) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| total = 0; | ||||||
| this.getStories().each(function (this:any, index:any) { | ||||||
| total += $(this).data('this').getPoints(); | ||||||
| }); | ||||||
| this.$.children('.header').children('.velocity').text(total); | ||||||
| }, | ||||||
|
|
||||||
| recalcOddity() { | ||||||
| this.$.find('.story:even').removeClass('odd').addClass('even'); | ||||||
| this.$.find('.story:odd').removeClass('even').addClass('odd'); | ||||||
| }, | ||||||
| }); | ||||||
| }(jQuery)); | ||||||
| export class Backlog { | ||||||
| private $:JQuery; | ||||||
| private el:HTMLElement; | ||||||
| burndown:Burndown; | ||||||
|
|
||||||
| constructor(el:HTMLElement) { | ||||||
| this.$ = $(el); | ||||||
| this.el = el; | ||||||
|
|
||||||
| // Associate this object with the element for later retrieval | ||||||
| this.$.data('this', this); | ||||||
|
|
||||||
| // Make the list sortable | ||||||
| this.getList().sortable({ | ||||||
| connectWith: '.stories', | ||||||
| dropOnEmpty: true, | ||||||
| start: this.dragStart, | ||||||
|
Check failure on line 60 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
|
||||||
| stop: this.dragStop, | ||||||
|
Check failure on line 61 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
|
||||||
| update: this.dragComplete, | ||||||
|
Check failure on line 62 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
|
||||||
| receive: this.dragChanged, | ||||||
|
Check failure on line 63 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
|
||||||
| remove: this.dragChanged, | ||||||
|
Check failure on line 64 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
|
||||||
| containment: $('#backlogs_container'), | ||||||
| cancel: 'input, textarea, button, select, option, .prevent_drag', | ||||||
| scroll: true, | ||||||
| helper(ui) { | ||||||
| const $clone = $(ui).clone(); | ||||||
| $clone.css('position', 'absolute'); | ||||||
| return $clone.get(0) as unknown as Element; | ||||||
| }, | ||||||
| }); | ||||||
|
|
||||||
| // Observe menu items | ||||||
| this.$.find('.add_new_story').click(this.handleNewStoryClick); | ||||||
|
Check failure on line 76 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
|
||||||
|
|
||||||
| if (this.isSprintBacklog()) { | ||||||
| new EditableSprint(this.getSprint()[0]); | ||||||
| this.burndown = new Burndown(this.$.find('.show_burndown_chart')[0]); | ||||||
| this.burndown.setSprintId(this.getSprint().data('this').getID()); | ||||||
|
Check failure on line 81 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
|
||||||
| } | ||||||
|
|
||||||
| // Initialize each item in the backlog | ||||||
| this.getStories().each((index, element) => { | ||||||
| // refers to an element with class="story" | ||||||
| new EditableStory(element); | ||||||
| }); | ||||||
|
|
||||||
| if (this.isSprintBacklog()) { | ||||||
| this.refresh(); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| dragChanged(e:JQueryEventObject, ui:JQueryUI.SortableUIParams) { | ||||||
|
Check failure on line 95 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
|
||||||
| $(this).parents('.backlog').data('this').refresh(); | ||||||
|
Check failure on line 96 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
|
||||||
| } | ||||||
|
|
||||||
| dragComplete(e:JQueryEventObject, ui:JQueryUI.SortableUIParams) { | ||||||
| const isDropTarget = (ui.sender === null || ui.sender === undefined); | ||||||
|
|
||||||
| // jQuery triggers dragComplete of source and target. | ||||||
| // Thus we have to check here. Otherwise, the story | ||||||
| // would be saved twice. | ||||||
| if (isDropTarget) { | ||||||
| ui.item.data('this').saveDragResult(); | ||||||
|
Check failure on line 106 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
|
||||||
| } | ||||||
| } | ||||||
|
|
||||||
| dragStart(e:JQueryEventObject, ui:JQueryUI.SortableUIParams) { | ||||||
| ui.item.addClass('dragging'); | ||||||
| } | ||||||
|
|
||||||
| dragStop(e:JQueryEventObject, ui:JQueryUI.SortableUIParams) { | ||||||
| ui.item.removeClass('dragging'); | ||||||
| } | ||||||
|
|
||||||
| getSprint() { | ||||||
| return $(this.el).find('.model.sprint').first(); | ||||||
| } | ||||||
|
|
||||||
| getStories() { | ||||||
| return this.getList().children('.story'); | ||||||
| } | ||||||
|
|
||||||
| getList() { | ||||||
| return this.$.children('.stories').first(); | ||||||
| } | ||||||
|
|
||||||
| handleNewStoryClick(e:JQuery.Event) { | ||||||
| const toggler = $(this).parents('.header').find('.toggler'); | ||||||
|
||||||
| const toggler = $(this).parents('.header').find('.toggler'); | |
| const toggler = $(e.currentTarget).parents('.header').find('.toggler'); |
Check failure on line 160 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
GitHub Actions / eslint
[eslint] frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts#L160 <@typescript-eslint/no-explicit-any>(https://typescript-eslint.io/rules/no-explicit-any)
Unexpected any. Specify a different type.
Raw output
{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":160,"column":15,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":160,"endColumn":18,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4805,4808],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4805,4808],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}
Check failure on line 170 in frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts
GitHub Actions / eslint
[eslint] frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts#L170 <@typescript-eslint/no-unsafe-argument>(https://typescript-eslint.io/rules/no-unsafe-argument)
Unsafe argument of type `any` assigned to a parameter of type `string | number | boolean | ((this: HTMLElement, index: number, text: string) => string | number | boolean)`.
Raw output
{"ruleId":"@typescript-eslint/no-unsafe-argument","severity":2,"message":"Unsafe argument of type `any` assigned to a parameter of type `string | number | boolean | ((this: HTMLElement, index: number, text: string) => string | number | boolean)`.","line":170,"column":59,"nodeType":"Identifier","messageId":"unsafeArgument","endLine":170,"endColumn":64}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method
handleNewStoryClickis passed as a callback without binding its context. When jQuery calls this callback,thiswill refer to the clicked DOM element, not the Backlog instance. This causesthis.newStory()on line 136 to fail. Bind the method using an arrow function or.bind(this):this.$.find('.add_new_story').click((e) => this.handleNewStoryClick(e))