-
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?
Conversation
dc48590 to
8225553
Compare
e9b3034 to
443c01a
Compare
fe0ba07 to
7f853c6
Compare
| let newInput = self.findFactory(typeId, statusId, 'status_id'); | ||
| newInput = self.prepareInputFromFactory(newInput, fieldId, 'status_id', fieldOrder, maxTabIndex); | ||
| // @ts-expect-error TS(2683): 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message | ||
| newInput = self.replaceStatusForNewType(input, newInput, $(this).parent().find('.status_id').val(), editor); |
Check warning
Code scanning / CodeQL
Useless assignment to local variable Warning
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 6 days ago
To address the issue, the assignment to newInput on line 193 should be removed, leaving only the function call to self.replaceStatusForNewType(...). This change will not alter the functional behavior of the code but will prevent confusion and clarify that the method is called for its side effect. No additional imports or definitions are necessary. The edit should be made only to the relevant line in the callback within the input.change handler.
-
Copy modified line R193
| @@ -190,7 +190,7 @@ | ||
| let newInput = self.findFactory(typeId, statusId, 'status_id'); | ||
| newInput = self.prepareInputFromFactory(newInput, fieldId, 'status_id', fieldOrder, maxTabIndex); | ||
| // @ts-expect-error TS(2683): 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message | ||
| newInput = self.replaceStatusForNewType(input, newInput, $(this).parent().find('.status_id').val(), editor); | ||
| self.replaceStatusForNewType(input, newInput, $(this).parent().find('.status_id').val(), editor); | ||
| }); | ||
| } else { | ||
| input = $(`#${fieldName}_options`).clone(true); |
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.
Pull Request Overview
This PR refactors the legacy backlog JavaScript code from a prototype-based object creation pattern (using the deprecated RB.Object.create() factory) to modern ES6 classes with TypeScript types. The migration modernizes the codebase by:
- Converting old-style
RB.Factory.initialize()patterns to direct class instantiation usingnew - Replacing function-based inheritance with ES6 class-based inheritance
- Adding proper TypeScript type annotations for jQuery event handlers
- Removing
@ts-expect-errorcomments for resolved type issues - Improving code organization with proper imports and exports
Reviewed Changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
frontend/src/typings/shims.d.ts |
Adds jQuery plugin type declarations for colorcontrast method |
frontend/src/stimulus/controllers/dynamic/backlogs/common.ts |
Converts RB utility objects to classes and adds TypeScript interfaces |
frontend/src/stimulus/controllers/dynamic/backlogs/model.ts |
Converts Model from factory pattern to ES6 class with proper constructor |
frontend/src/stimulus/controllers/dynamic/backlogs/work_package.ts |
Converts WorkPackage to abstract ES6 class extending Model |
frontend/src/stimulus/controllers/dynamic/backlogs/task.ts |
Converts Task to ES6 class extending WorkPackage |
frontend/src/stimulus/controllers/dynamic/backlogs/story.ts |
Converts Story to ES6 class with mixin pattern for EditableInplace |
frontend/src/stimulus/controllers/dynamic/backlogs/sprint.ts |
Converts Sprint to ES6 class with mixin pattern |
frontend/src/stimulus/controllers/dynamic/backlogs/impediment.ts |
Converts Impediment to ES6 class extending Task |
frontend/src/stimulus/controllers/dynamic/backlogs/taskboard.ts |
Converts Taskboard to ES6 class with proper constructor and methods |
frontend/src/stimulus/controllers/dynamic/backlogs/backlog.ts |
Converts Backlog to ES6 class replacing factory initialization |
frontend/src/stimulus/controllers/dynamic/backlogs/burndown.ts |
Converts Burndown to ES6 class |
frontend/src/stimulus/controllers/dynamic/backlogs/editable_inplace.ts |
Converts mixin to TypeScript higher-order function pattern |
frontend/src/stimulus/controllers/dynamic/backlogs/master_backlog.ts |
Updates to use direct class instantiation |
frontend/src/stimulus/controllers/dynamic/backlogs/show_main.ts |
Updates to use direct class instantiation |
frontend/src/assets/sass/backlogs/_taskboard.sass |
Updates comment to reference new class constructor |
| }); | ||
|
|
||
| // Observe menu items | ||
| this.$.find('.add_new_story').click(this.handleNewStoryClick); |
Copilot
AI
Nov 5, 2025
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 handleNewStoryClick is passed as a callback without binding its context. When jQuery calls this callback, this will refer to the clicked DOM element, not the Backlog instance. This causes this.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))
| this.$.find('.add_new_story').click(this.handleNewStoryClick); | |
| this.$.find('.add_new_story').click((e) => this.handleNewStoryClick(e)); |
| } | ||
|
|
||
| handleNewStoryClick(e:JQuery.Event) { | ||
| const toggler = $(this).parents('.header').find('.toggler'); |
Copilot
AI
Nov 5, 2025
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 uses $(this) expecting it to be the DOM element, but when called as a class method, this refers to the Backlog instance. This conflicts with the callback binding issue in line 76. Either change line 76 to bind properly and use this.$ here, or change the method to be a standalone function that receives the correct context.
| const toggler = $(this).parents('.header').find('.toggler'); | |
| const toggler = $(e.currentTarget).parents('.header').find('.toggler'); |
| this.$.find('#tasks .add_new.clickable').click(this.handleAddNewTaskClick); | ||
| this.$.find('#impediments .add_new.clickable').click(this.handleAddNewImpedimentClick); |
Copilot
AI
Nov 5, 2025
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 methods handleAddNewTaskClick and handleAddNewImpedimentClick are passed as callbacks without binding context. When jQuery invokes these callbacks, this will refer to the clicked DOM element, not the Taskboard instance. Both methods use $(this) expecting the DOM element, but also need to access instance methods. Use arrow functions to preserve context: this.$.find('#tasks .add_new.clickable').click((e) => this.handleAddNewTaskClick(e))
| this.$.find('#tasks .add_new.clickable').click(this.handleAddNewTaskClick); | |
| this.$.find('#impediments .add_new.clickable').click(this.handleAddNewImpedimentClick); | |
| this.$.find('#tasks .add_new.clickable').click((e) => this.handleAddNewTaskClick(e)); | |
| this.$.find('#impediments .add_new.clickable').click((e) => this.handleAddNewImpedimentClick(e)); |
| const row = $(this).parents('tr').first(); | ||
| $('#taskboard').data('this').newImpediment(row); | ||
| } | ||
|
|
||
| handleAddNewTaskClick(e:JQuery.Event) { | ||
| const row = $(this).parents('tr').first(); |
Copilot
AI
Nov 5, 2025
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.
These methods expect this to be the DOM element (lines 152, 157 use $(this)), which is inconsistent with ES6 class methods where this refers to the instance. This creates a context mismatch between the implementation and how the methods are registered as callbacks on lines 72-73.
| const row = $(this).parents('tr').first(); | |
| $('#taskboard').data('this').newImpediment(row); | |
| } | |
| handleAddNewTaskClick(e:JQuery.Event) { | |
| const row = $(this).parents('tr').first(); | |
| const row = $(e.currentTarget).parents('tr').first(); | |
| $('#taskboard').data('this').newImpediment(row); | |
| } | |
| handleAddNewTaskClick(e:JQuery.Event) { | |
| const row = $(e.currentTarget).parents('tr').first(); |
| data += `&${this.$.find('.editor').serialize()}`; | ||
| } | ||
|
|
||
| //TODO: this might be unsave in case the parent of this story is not the |
Copilot
AI
Nov 5, 2025
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.
Corrected spelling of 'unsave' to 'unsafe'.
| //TODO: this might be unsave in case the parent of this story is not the | |
| //TODO: this might be unsafe in case the parent of this story is not the |
| let newInput = self.findFactory(typeId, statusId, 'status_id'); | ||
| newInput = self.prepareInputFromFactory(newInput, fieldId, 'status_id', fieldOrder, maxTabIndex); | ||
| // @ts-expect-error TS(2683): 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message | ||
| newInput = self.replaceStatusForNewType(input, newInput, $(this).parent().find('.status_id').val(), editor); |
Copilot
AI
Nov 5, 2025
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 value assigned to newInput here is unused.
| newInput = self.replaceStatusForNewType(input, newInput, $(this).parent().find('.status_id').val(), editor); | |
| self.replaceStatusForNewType(input, newInput, $(this).parent().find('.status_id').val(), editor); |
|
@copilot please go through this draft conversion of a very old part of the codebase from "Crockford Objects"/custom Object factories ( You may want to start with the suggestions that you provided as comments in this Pull Request: Do the following before committing or pushing: Although there are no test cases that cover these particular files, this will at least check TypeScript compiles. |
Ticket
https://community.openproject.org/wp/66575
What are you trying to accomplish?
Migrates backlogs from Crockford Objects to JavaScript classes.
Screenshots
What approach did you choose and why?
Merge checklist