Skip to content

Commit

Permalink
Merge pull request #16020 from martenson/jobs-filtering
Browse files Browse the repository at this point in the history
implement admin jobs filtering
  • Loading branch information
dannon authored May 5, 2023
2 parents 4e8b2fe + 17c2e26 commit ec7d2ad
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 24 deletions.
27 changes: 26 additions & 1 deletion client/src/components/Indices/filtersMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,39 @@ export default {
components: {
IndexFilter,
},
props: {
inputDebounceDelay: {
type: Number,
default: 500,
},
},
data() {
return {
filter: "",
implicitFilter: null,
helpHtml: null,
};
},
computed: {
isFiltered() {
return !!this.filter;
return Boolean(this.filter);
},
effectiveFilter() {
let filter = this.filter;
const implicitFilter = this.implicitFilter;
if (implicitFilter && filter) {
filter = `${implicitFilter} ${filter}`;
} else if (implicitFilter) {
filter = implicitFilter;
}
return filter;
},
filterAttrs() {
return {
"debounce-delay": this.inputDebounceDelay,
placeholder: this.titleSearch,
"help-html": this.helpHtml,
};
},
},
methods: {
Expand Down
12 changes: 12 additions & 0 deletions client/src/components/Indices/filtersMixin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,16 @@ describe("filtersMixin.js", () => {
wrapper.vm.appendTagFilter("name", "foobar");
expect(wrapper.vm.filter).toBe("name:'foobar'");
});

it("should have an effective filter that combines implicit and explicit filter", async () => {
wrapper.vm.implicitFilter = "tag:cowdog";
wrapper.vm.appendTagFilter("name", "foobar");
expect(wrapper.vm.filter).toBe("name:'foobar'");
expect(wrapper.vm.effectiveFilter).toBe("tag:cowdog name:'foobar'");
});

it("should just use implicit filter as effective if filter is empty", async () => {
wrapper.vm.implicitFilter = "tag:cowdog";
expect(wrapper.vm.effectiveFilter).toBe("tag:cowdog");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{{ message }}
</b-alert>
<Heading h2 size="md" separator>Job Lock</Heading>
<job-lock />
<JobLock />
<Heading h2 size="md" separator>Job Overview</Heading>
<p>
Below unfinished jobs are displayed (in the 'new', 'queued', 'running', or 'upload' states) and recently
Expand Down Expand Up @@ -36,9 +36,7 @@
</b-form-group>
</b-form>
<b-form-group description="Use strings or regular expressions to search jobs.">
<b-input-group id="filter-regex">
<b-form-input v-model="filter" placeholder="Type to Search" @keyup.esc.native="filter = ''" />
</b-input-group>
<IndexFilter v-bind="filterAttrs" id="job-search" v-model="filter" />
</b-form-group>
</b-col>
</b-row>
Expand All @@ -56,11 +54,10 @@
</b-form>
</transition>
<h3 class="mb-0 h-sm">Unfinished Jobs</h3>
<jobs-table
<JobsTable
v-model="jobsItemsModel"
:fields="unfinishedJobFields"
:items="unfinishedJobs"
:filter="filter"
:table-caption="runningTableCaption"
:no-items-message="runningNoJobsMessage"
:loading="loading"
Expand All @@ -78,19 +75,22 @@
:checked="allSelected"
:value="data.item['id']"></b-form-checkbox>
</template>
</jobs-table>
</JobsTable>

<template v-if="!showAllRunning">
<h3 class="mb-0 h-sm">Finished Jobs</h3>
<jobs-table
<JobsTable
:table-caption="finishedTableCaption"
:fields="finishedJobFields"
:items="finishedJobs"
:filter="filter"
:no-items-message="finishedNoJobsMessage"
:loading="loading"
:busy="busy">
</jobs-table>
:busy="busy"
@tool-clicked="(toolId) => appendTagFilter('tool', toolId)"
@runner-clicked="(runner) => appendTagFilter('runner', runner)"
@handler-clicked="(handler) => appendTagFilter('handler', handler)"
@user-clicked="(user) => appendTagFilter('user', user)">
</JobsTable>
</template>
</div>
</template>
Expand All @@ -105,14 +105,42 @@ import { commonJobFields } from "./JobFields";
import { errorMessageAsString } from "utils/simple-error";
import { jobsProvider } from "components/providers/JobProvider";
import Heading from "components/Common/Heading";
import filtersMixin from "components/Indices/filtersMixin";
function cancelJob(jobId, message) {
const url = `${getAppRoot()}api/jobs/${jobId}`;
return axios.delete(url, { data: { message: message } });
}
const helpHtml = `<div>
<p>This textbox box can be used to filter the jobs displayed.
<p>Text entered here will be searched against job user, tool ID, job runner, and handler. Additionally,
advanced filtering tags can be used to refine the search more precisely. Tags are of the form
<code>&lt;tag_name&gt;:&lt;tag_value&gt;</code> or <code>&lt;tag_name&gt;:'&lt;tag_value&gt;'</code>.
For instance to search just for jobs with <code>cat1</code> in the tool name, <code>tool:cat1</code> can be used.
Notice by default the search is not case-sensitive.
<p>If the quoted version of tag is used, the search is case sensitive and only full matches will be
returned. So <code>tool:'cat1'</code> would show only jobs from the <code>cat1</code> tool exactly.</p>
<p>The available tags are:
<dl>
<dt><code>user</code></dt>
<dd>This filters the job index to contain only jobs executed by matching user(s). You may also just click on a user in the list of jobs to filter on that exact user using this directly.</dd>
<dt><code>handler</code></dt>
<dd>This filters the job index to contain only jobs executed on matching handler(s). You may also just click on a handler in the list of jobs to filter on that exact user using this directly.</dd>
<dt><code>runner</code></dt>
<dd>This filters the job index to contain only jobs executed on matching job runner(s). You may also just click on a runner in the list of jobs to filter on that exact user using this directly.</dd>
<dt><code>tool</code></dt>
<dd>This filters the job index to contain only jobs from the matching tool(s). You may also just click on a tool in the list of jobs to filter on that exact user using this directly.</dd>
</dl>
</div>
`;
export default {
components: { JobLock, JobsTable, Heading },
mixins: [filtersMixin],
data() {
return {
jobs: [],
Expand All @@ -130,13 +158,14 @@ export default {
allSelected: false,
indeterminate: false,
stopMessage: "",
filter: "",
message: "",
status: "info",
loading: true,
busy: true,
cutoffMin: 5,
showAllRunning: false,
titleSearch: `search jobs`,
helpHtml: helpHtml,
};
},
computed: {
Expand Down Expand Up @@ -205,6 +234,9 @@ export default {
const dateRangeMin = new Date(Date.now() - cutoff * 60 * 1000).toISOString();
params.date_range_min = `${dateRangeMin}`;
}
if (this.filter) {
params.search = this.filter;
}
const ctx = {
root: getAppRoot(),
...params,
Expand Down
41 changes: 33 additions & 8 deletions client/src/components/admin/JobsTable.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<template>
<div>
<div class="jobs-table-wrapper">
<b-table
v-model="innerValue"
:fields="fields"
:items="items"
:filter="filter"
hover
responsive
no-sort-reset
striped
caption-top
:busy="busy"
class="jobs-table"
show-empty
@row-clicked="toggleDetails">
<template v-slot:table-caption>
Expand All @@ -23,10 +23,39 @@
</b-alert>
</template>
<template v-slot:cell(update_time)="data">
<utc-date :date="data.value" mode="elapsed" />
<UtcDate :date="data.value" mode="elapsed" />
</template>
<template v-slot:row-details="row">
<job-details :job="row.item" />
<JobDetails :job="row.item" />
</template>
<template v-slot:cell(user_email)="data">
<a class="job-filter-link-user" :data-user="data.value" @click="$emit('user-clicked', data.value)">{{
data.value
}}</a>
</template>
<template v-slot:cell(tool_id)="data">
<a
class="job-filter-link-tool-id"
:data-tool-id="data.value"
@click="$emit('tool-clicked', data.value)"
>{{ data.value }}</a
>
</template>
<template v-slot:cell(job_runner_name)="data">
<a
class="job-filter-link-runner"
:data-runner="data.value"
@click="$emit('runner-clicked', data.value)"
>{{ data.value }}</a
>
</template>
<template v-slot:cell(handler)="data">
<a
class="job-filter-link-handler"
:data-handler="data.handler"
@click="$emit('handler-clicked', data.value)"
>{{ data.value }}</a
>
</template>
<template v-for="(index, name) in $slots" v-slot:[name]>
<slot :name="name" />
Expand Down Expand Up @@ -56,10 +85,6 @@ export default {
items: {
required: true,
},
filter: {
type: String,
required: true,
},
busy: {
type: Boolean,
required: true,
Expand Down
4 changes: 2 additions & 2 deletions client/src/entry/analysis/routes/admin-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import DisplayApplications from "components/admin/DisplayApplications";
import ErrorStack from "components/admin/ErrorStack";
import FormGeneric from "components/Form/FormGeneric";
import Grid from "components/Grid/Grid";
import Jobs from "components/admin/Jobs";
import JobsList from "components/admin/JobsList";
import RegisterForm from "components/Login/RegisterForm";
import ResetMetadata from "components/admin/ResetMetadata";
import SanitizeAllow from "components/admin/SanitizeAllow";
Expand Down Expand Up @@ -42,7 +42,7 @@ export default [
{ path: "display_applications", component: DisplayApplications },
{ path: "error_stack", component: ErrorStack },
{ path: "invocations", component: ActiveInvocations },
{ path: "jobs", component: Jobs },
{ path: "jobs", component: JobsList },
{ path: "reset_metadata", component: ResetMetadata },
{ path: "sanitize_allow", component: SanitizeAllow },
{ path: "toolbox_dependencies", component: ToolboxDependencies },
Expand Down
17 changes: 17 additions & 0 deletions client/src/utils/navigation/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ masthead:

# Shared data
libraries: 'Data Libraries'
published_workflows: 'Workflows'
published_histories: 'Histories'
published_pages: 'Pages'

preferences:
selectors:
Expand Down Expand Up @@ -480,6 +482,10 @@ pages:
create: '.manage-table-actions .action-button'
submit: '#submit'
export: '.markdown-pdf-export'
dropdown: '[data-page-dropdown*="${id}"]'
index_table: "#page-table"
index_rows: "#page-table > tbody > tr:not(.b-table-empty-row, [style*='display: none'])"

editor:
selectors:
save: '#save-button'
Expand Down Expand Up @@ -544,6 +550,7 @@ workflows:
pager_page_last: '.gx-workflows-grid-pager .gx-grid-pager-last button'
pager_page_previous: '.gx-workflows-grid-pager .gx-grid-pager-prev button'
pager_page_active: '.gx-workflows-grid-pager .gx-grid-pager-page.active button'
dropdown: '[data-workflow-dropdown*="${id}"]'
run_button: '[data-workflow-run*="${id}"]'
bookmark_link: '.workflow-bookmark-link'
workflow_with_name:
Expand Down Expand Up @@ -783,6 +790,16 @@ admin:
type: xpath
selector: "//label[@for='prevent-job-dispatching']/strong"
cutoff: '#cutoff'
table: '.jobs-table'
filter_link_user: '.job-filter-link-user'
filter_link_tool: '.job-filter-link-tool-id'
filter_link_handler: '.job-filter-link-handler'
filter_link_runner: '.job-filter-link-runner'
filter_link_by_user: '.job-filter-link-user[data-user="${user}"]'
filter_link_by_tool: '.job-filter-link-tool-id[data-tool-id="${tool_id}"]'
filter_link_by_handler: '.job-filter-link-handler[data-handler="${handler}"]'
filter_link_by_runner: '.job-filter-link-runner[data-runner="${runner}"]'
filter_query: '.index-filter-query'

toolshed:
selectors:
Expand Down
36 changes: 35 additions & 1 deletion lib/galaxy/selenium/navigates_galaxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ def timeout_for(self, wait_type: WaitType = DEFAULT_WAIT_TYPE, **kwd) -> float:
def home(self) -> None:
"""Return to root Galaxy page and wait for some basic widgets to appear."""
self.get()
self.components.masthead._.wait_for_visible()
try:
self.components.masthead._.wait_for_visible()
except SeleniumTimeoutException as e:
raise ClientBuildException(e)

def go_to_trs_search(self) -> None:
self.driver.get(self.build_url("workflows/trs_search"))
Expand Down Expand Up @@ -1080,6 +1083,21 @@ def navigate_to_pages(self):
self.click_masthead_user()
self.components.masthead.pages.wait_for_and_click()

def navigate_to_published_workflows(self):
self.home()
self.click_masthead_shared_data()
self.components.masthead.published_workflows.wait_for_and_click()

def navigate_to_published_histories_page(self):
self.home()
self.click_masthead_shared_data()
self.components.masthead.published_histories.wait_for_and_click()

def navigate_to_published_pages(self):
self.home()
self.click_masthead_shared_data()
self.components.masthead.published_pages.wait_for_and_click()

def admin_open(self):
self.components.masthead.admin.wait_for_and_click()

Expand Down Expand Up @@ -1241,6 +1259,16 @@ def clear_tooltips(self):
action_chains.move_to_element(center_element).perform()
self.wait_for_selector_absent_or_hidden(".b-tooltip", wait_type=WAIT_TYPES.UX_POPUP)

def pages_index_table_elements(self):
pages = self.components.pages
pages.index_table.wait_for_visible()
return pages.index_rows.all()

def page_index_click_option(self, option_title, page_id):
self.components.pages.dropdown(id=page_id).wait_for_and_click()
if not self.select_dropdown_item(option_title):
raise AssertionError(f"Failed to find page action option with title [{option_title}]")

def workflow_index_open(self):
self.home()
self.click_masthead_workflow()
Expand Down Expand Up @@ -2065,3 +2093,9 @@ def __init__(self, timeout_exception, user_info, dom_message):
template = "Waiting for UI to reflect user logged in but it did not occur. API indicates no user is currently logged in. %s API response was [%s]. %s"
msg = template % (dom_message, user_info, timeout_exception.msg)
super().__init__(msg=msg, screen=timeout_exception.screen, stacktrace=timeout_exception.stacktrace)


class ClientBuildException(SeleniumTimeoutException):
def __init__(self, timeout_exception: SeleniumTimeoutException):
msg = f"Error waiting for Galaxy masthead to appear, this frequently means there is a problem with the client build and the Galaxy client is broken. {timeout_exception.msg}"
super().__init__(msg=msg, screen=timeout_exception.screen, stacktrace=timeout_exception.stacktrace)
Loading

0 comments on commit ec7d2ad

Please sign in to comment.