Skip to content
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

Merged
merged 23 commits into from Jun 27, 2019

Conversation

lowellrex
Copy link
Contributor

@lowellrex lowellrex commented Jun 25, 2019

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.

caching-pagination

@lowellrex lowellrex self-assigned this Jun 25, 2019
@codeclimate
Copy link

codeclimate bot commented Jun 25, 2019

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]
Copy link
Contributor Author

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
Copy link
Contributor Author

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.

Copy link
Contributor

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)

Copy link
Contributor Author

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:

  1. 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.
  2. 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's use_task_pages_api? function definition) without affecting any of the teams who are not experiencing team queue loading problems.
  3. 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

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

}
end

private

def use_task_pages_api?
# return true
Copy link
Contributor Author

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) : []
Copy link
Contributor Author

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,
Copy link
Contributor Author

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,
Copy link
Contributor Author

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`;
Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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}
Copy link
Contributor Author

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}
Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor

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();
};
Copy link
Contributor Author

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.

Copy link
Contributor

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) => {
Copy link
Contributor Author

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.

Copy link
Contributor Author

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?).

Copy link
Contributor

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.

https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/asyncableJobs/pages/JobsPage.jsx#L210

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

Copy link
Contributor

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) => {
Copy link
Contributor Author

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,
Copy link
Contributor Author

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.

@va-bot
Copy link
Collaborator

va-bot commented Jun 26, 2019

1 Warning
⚠️ This is a Big PR. Try to break this down if possible.

Generated by 🚫 Danger

Copy link
Contributor

@pkarman pkarman left a 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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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?
Copy link
Contributor

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
Copy link
Contributor

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`;
Copy link
Contributor

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}
Copy link
Contributor

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();
};
Copy link
Contributor

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) => {
Copy link
Contributor

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.

https://github.com/department-of-veterans-affairs/caseflow/blob/master/client/app/asyncableJobs/pages/JobsPage.jsx#L210

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

@@ -286,6 +286,15 @@ export default class QueueTable extends React.PureComponent {
return paginatedData;
}

componentDidMount = () => {
this.setState({
tasksFromApiForPage: {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

@pkarman pkarman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love it. 🚢

Copy link
Contributor

@kevmo kevmo left a 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!

@lowellrex lowellrex added the Ready-to-Merge This PR is ready to be merged and will be picked up by va-bot to automatically merge to master label Jun 27, 2019
@va-bot va-bot merged commit 9495744 into master Jun 27, 2019
@va-bot va-bot deleted the lowell/11054_frontend_pagination_table branch June 27, 2019 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Ready-to-Merge This PR is ready to be merged and will be picked up by va-bot to automatically merge to master
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants