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
Enable org queues to use task pagination API #11213
Conversation
Code Climate has analyzed commit 7e872f9 and detected 0 issues on this pull request. View more on Code Climate. |
@@ -17,6 +17,8 @@ def index | |||
private | |||
|
|||
def tasks | |||
return [] if queue_config[:use_task_pages_api] |
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.
If we are using the back-end pagination API then we should not bother with returning the tasks here since we will use the ones from the queue config for the first page and the API for all subsequent pages.
@@ -24,6 +24,10 @@ def show_regional_office_in_queue? | |||
false | |||
end | |||
|
|||
def use_task_pages_api? | |||
false | |||
end |
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.
By default organizations will continue using front-end pagination.
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.
could we make this use the FeatureToggle rather than hardcoded bool?
FeatureToggle.enabled?(:use_task_pages_api)
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.
FeatureToggle's are enabled on a per-user basis and this flag allows us to enable it on a per-organization basis which gives us another level of control as we roll this out. When this code is release live nothing should change. No requests will use the task pages API. However, we can roll this out in a phased fashion as follows:
- We can enable the feature toggle for a few members of teams with this flag set to true (VSOs, VLJ support, Hearings management) so they can test that it works and doesn't have any unexpected bugs without affecting how the rest of that team is using the task queue.
- After individual folks have confirmed that the task pages API is working well we can roll it out to a whole team that needs the task pages API (we'd actually need to move the
enabled?
function call inside the organization'suse_task_pages_api?
function definition) without affecting any of the teams who are not experiencing team queue loading problems. - After we've caught some bugs with heavy full-team use we can roll it out to all organization queues (by removing this function entirely and making it the default).
This function at the organization enables phase 2 which I think is fairly important. Team queues work for most teams right now, so I'd love to limit the pagination changes to just those teams that are experiencing team queue loading problems before rolling the changes out to everybody.
def self.singleton | ||
HearingsManagement.first || HearingsManagement.create(name: "Hearings Management", url: "hearings-management") | ||
end | ||
|
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.
Moving the singleton
definition to the top of the class definition so that it matches all of the other organizations.
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.
HearingAdmin
team could use same change.
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.
Totally agree. Going to postpone rearranging that class to a later PR so that this changeset stays small.
app/models/queue_config.rb
Outdated
} | ||
end | ||
|
||
private | ||
|
||
def use_task_pages_api? | ||
# return 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.
My manual toggle for easy testing locally. Will remove before this is merged.
|
||
# Only return tasks in the configuration if we are using it to populate the first page of results. | ||
# Otherwise avoid the overhead of the additional database requests. | ||
tasks = use_task_pages_api? ? serialized_tasks_for_user(task_pager.paged_tasks, user) : [] |
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.
Inverse of the optimization above. Don't make additional requests to the database if we are just going to be using the tasks we get from some other method.
label: format(COPY::ORGANIZATIONAL_QUEUE_PAGE_UNASSIGNED_TAB_TITLE, tasks.count), | ||
name: name, | ||
label: COPY::ORGANIZATIONAL_QUEUE_PAGE_UNASSIGNED_TAB_TITLE, | ||
name: Constants.QUEUE_CONFIG.UNASSIGNED_TASKS_TAB_NAME, |
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.
Was able to consolidate all this duplicated logic in the new attach_tasks_to_tab
method.
currentPage, | ||
numberOfPages, |
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.
Pass numberOfPages
as an argument instead of computing it from the paginatedData
argument.
// Create the entire summary | ||
const paginationSummary = `Viewing ${currentCaseRange} of ${totalCasesCount} total cases`; | ||
|
||
const paginationSummary = `Viewing ${beginningCaseNumber}-${endingCaseNumber} of ${totalCasesCount} total cases`; |
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.
No significant change here other than we were able to replace the paginatedData
argument (which relied on an array of array of tasks to determine this summary) with the casesPerPage
argument (which is a number).
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.
Not sure if you looked at the Pagination component? I did something similar there for the /jobs pagination.
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.
It's fairly easy to replace the only place we use TablePagination
with the Pagination
component so I'm going to go ahead and do that instead.
{ tabConfig.allow_bulk_assign && <BulkAssignButton /> } | ||
<TaskTable | ||
customColumns={cols} | ||
key={tabConfig.name} |
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.
Need to set a key
on each TaskTable
so that they can have their own state (React ended up sharing state across the sibling components without this key).
tasksPerPage={config.tasks_per_page} | ||
numberOfPages={tabConfig.task_page_count} | ||
totalTaskCount={tabConfig.total_task_count} | ||
taskPagesApiEndpoint={tabConfig.task_page_endpoint_base_path} |
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.
Since we're adding 5 new arguments here maybe it makes sense to just make this a new component? What're y'all's thoughts?
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.
a PaginatedTaskTable
makes sense to me. I think the nonComp feature still uses the TaskTable
so otherwise we'd want to make sure we don't introduce regressions.
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.
If we believe we will ever want to use the PaginatedTaskTable
in other views, I have a slight preference for creating it in its own file in client/app/queue/components
.
el.setAttribute('tabindex', '-1'); | ||
} | ||
el.focus(); | ||
}; |
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.
I got sick of the browser taking control of the scroll when I was testing this. Happy to put it back if we want, but I really hate this.
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.
hate is a good motivator.
} | ||
|
||
requestNewPage = (newPage) => { |
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.
This is the heart of the new behaviour. We make an API request if we are using the back-end pagination API.
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.
This method seems more than a little hacky to me. Very open to ideas for how to improve it (Maybe it should be injected from OrganizationQueue
? How would we set local state? Maybe it should just be a separate component?).
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.
This was the part I struggled with on the /jobs paginator too. I am not sure there is an elegant solution. In my case, I wasn't trying to adhere to SPA pattern so just reloaded the whole page.
This looks readable to me, which is what I would care about. The only gotchas are the off-by-one with index and page counts. You may also want to guard against clicking on the current page (not sure if that affects here as it did for me on /jobs).
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.
I agree that this is readable, which is most important. The comments in the code are good. I think this general approach is definitely fine for now.
@@ -52,79 +52,85 @@ export const prepareMostRecentlyHeldHearingForStore = (appealId, hearing) => { | |||
}; | |||
}; | |||
|
|||
const taskAttributesFromRawTask = (task) => { |
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.
Separating these functions into two steps each so we can get arrays for our task pages instead of the hashes that are sent to the global redux store.
description: format(COPY::ALL_CASES_QUEUE_TABLE_TAB_DESCRIPTION, organization.name), | ||
columns: [ | ||
Constants.QUEUE_CONFIG.CASE_DETAILS_LINK_COLUMN, | ||
Constants.QUEUE_CONFIG.ISSUE_COUNT_COLUMN, | ||
Constants.QUEUE_CONFIG.APPEAL_TYPE_COLUMN, | ||
Constants.QUEUE_CONFIG.DOCKET_NUMBER_COLUMN | ||
], | ||
task_group: Constants.QUEUE_CONFIG.TRACKING_TASKS_GROUP, |
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.
We never ended up using the task_group
element.
Generated by 🚫 Danger |
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.
I like the direction this is going!
@@ -24,6 +24,10 @@ def show_regional_office_in_queue? | |||
false | |||
end | |||
|
|||
def use_task_pages_api? | |||
false | |||
end |
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.
could we make this use the FeatureToggle rather than hardcoded bool?
FeatureToggle.enabled?(:use_task_pages_api)
@@ -8,4 +8,8 @@ def self.singleton | |||
def next_assignee(options = {}) | |||
ColocatedTaskDistributor.new.next_assignee(options) | |||
end | |||
|
|||
def use_task_pages_api? | |||
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.
same thing here about feature toggle.
def self.singleton | ||
HearingsManagement.first || HearingsManagement.create(name: "Hearings Management", url: "hearings-management") | ||
def use_task_pages_api? | ||
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.
feature toggle?
class Vso < Representative; end | ||
class Vso < Representative | ||
def use_task_pages_api? | ||
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.
feature toggle?
} | ||
end | ||
|
||
private | ||
|
||
def use_task_pages_api? | ||
# return true | ||
FeatureToggle.enabled?(:use_task_pages_api) && organization.use_task_pages_api? |
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.
+1
@@ -46,14 +46,22 @@ def paged_tasks | |||
# tasks | |||
# end | |||
|
|||
def task_page_count |
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.
does the kaminari total_pages
method work on tasks_for_tab
here?
// Create the entire summary | ||
const paginationSummary = `Viewing ${currentCaseRange} of ${totalCasesCount} total cases`; | ||
|
||
const paginationSummary = `Viewing ${beginningCaseNumber}-${endingCaseNumber} of ${totalCasesCount} total cases`; |
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.
Not sure if you looked at the Pagination component? I did something similar there for the /jobs pagination.
tasksPerPage={config.tasks_per_page} | ||
numberOfPages={tabConfig.task_page_count} | ||
totalTaskCount={tabConfig.total_task_count} | ||
taskPagesApiEndpoint={tabConfig.task_page_endpoint_base_path} |
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.
a PaginatedTaskTable
makes sense to me. I think the nonComp feature still uses the TaskTable
so otherwise we'd want to make sure we don't introduce regressions.
el.setAttribute('tabindex', '-1'); | ||
} | ||
el.focus(); | ||
}; |
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.
hate is a good motivator.
} | ||
|
||
requestNewPage = (newPage) => { |
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.
This was the part I struggled with on the /jobs paginator too. I am not sure there is an elegant solution. In my case, I wasn't trying to adhere to SPA pattern so just reloaded the whole page.
This looks readable to me, which is what I would care about. The only gotchas are the off-by-one with index and page counts. You may also want to guard against clicking on the current page (not sure if that affects here as it did for me on /jobs).
client/app/queue/QueueTable.jsx
Outdated
@@ -286,6 +286,15 @@ export default class QueueTable extends React.PureComponent { | |||
return paginatedData; | |||
} | |||
|
|||
componentDidMount = () => { | |||
this.setState({ | |||
tasksFromApiForPage: { |
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.
as I'm reading this, the idea seems to be cache each paged task set on the browser, rather than re-fetching on each page click?
I can see the attraction of that for performance reasons. My only concern would be once sorting/filtering are enabled, that the cache invalidation will be tricky. Also, if someone updates a task in a different browser, that the user in this browser won't see the latest when they page "back" to that task set.
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.
Correct. I think we'll have to handle caching some other way when we do sorting/filtering.
And you're totally right about the different browser window/tab causing cached tasks to be in the incorrect state (though we already have this now with entirely front-end pagination).
Happy to back out the caching of paged tasks if you think that's better.
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.
I think the caching falls under the category of premature optimization. Let's back it out. In theory the pagination itself should make things perform well enough that we don't need caching -- time will tell.
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.
love it. 🚢
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.
Looks good to go!
Connects #11054. Enables back-end pagination for certain organizations (VSOs, VLJ support staff, hearing management branch) when the current user has the
use_task_pages_api
feature toggle on. Still needs some cosmetic love, but it's far enough along that I'd like some feedback on how this approach can be improved.