Skip to content

Commit cacf25f

Browse files
authored
Merge pull request galaxyproject#22228 from itisAliRH/btable-to-gtable-workflows
Migrate Workflow Components from BTable to GTable
2 parents 760e6ab + 0659920 commit cacf25f

6 files changed

Lines changed: 116 additions & 129 deletions

File tree

client/src/components/JobStates/JobState.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const stateIcon = computed(() => iconClasses[props.job.state] || null);
2020
</script>
2121

2222
<template>
23-
<span class="rounded px-2 py-1" :class="badgeClass">
23+
<span class="rounded px-2 py-1 text-nowrap" :class="badgeClass">
2424
<FontAwesomeIcon v-if="stateIcon" :icon="stateIcon.icon" :spin="stateIcon.spin" />
2525
{{ props.job.state }}
2626
</span>

client/src/components/Workflow/Import/TrsSearch.vue

Lines changed: 39 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22
import { faTimes } from "@fortawesome/free-solid-svg-icons";
33
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
44
import axios from "axios";
5-
import { BAlert, BFormInput, BInputGroup, BInputGroupAppend, BTable } from "bootstrap-vue";
5+
import { BAlert, BCard, BFormInput, BInputGroup, BInputGroupAppend } from "bootstrap-vue";
66
import { computed, type Ref, ref, watch } from "vue";
77
import { useRouter } from "vue-router/composables";
88
9+
import type { RowClickEvent, TableField } from "@/components/Common/GTable.types";
910
import { getRedirectOnImportPath } from "@/components/Workflow/redirectPath";
1011
import { Services } from "@/components/Workflow/services";
1112
import { useMarkdown } from "@/composables/markdown";
1213
import { withPrefix } from "@/utils/redirect";
1314
14-
import type { TrsSelection } from "./types";
15+
import type { TrsSelection, TrsTool as TrsSearchData } from "./types";
1516
1617
import GButton from "@/components/BaseComponents/GButton.vue";
18+
import GTable from "@/components/Common/GTable.vue";
1719
import HelpText from "@/components/Help/HelpText.vue";
1820
import LoadingSpan from "@/components/LoadingSpan.vue";
1921
import TrsServerSelection from "@/components/Workflow/Import/TrsServerSelection.vue";
@@ -23,16 +25,16 @@ const emit = defineEmits<{
2325
(e: "input-valid", valid: boolean): void;
2426
}>();
2527
26-
type TrsSearchData = {
28+
type TrsSearchRow = {
2729
id: string;
2830
name: string;
2931
description: string;
30-
[key: string]: unknown;
32+
data: TrsSearchData;
3133
};
3234
3335
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true });
3436
35-
const fields = [
37+
const fields: TableField[] = [
3638
{ key: "name", label: "Name" },
3739
{ key: "description", label: "Description" },
3840
{ key: "organization", label: "Organization" },
@@ -99,23 +101,9 @@ function onTrsSelectionError(message: string) {
99101
errorMessage.value = message;
100102
}
101103
102-
function showRowDetails(row: any, _index: number, e: MouseEvent) {
103-
if ((e.target as Node | undefined)?.nodeName !== "A") {
104-
// Collapse all other rows
105-
itemsComputed.value.forEach((item) => {
106-
if (item !== row) {
107-
item._showDetails = false;
108-
}
109-
});
110-
// Toggle the clicked row
111-
const wasExpanded = row._showDetails;
112-
row._showDetails = !row._showDetails;
113-
114-
// If collapsing the row, reset selection state
115-
if (wasExpanded) {
116-
selectedTool.value = null;
117-
selectedVersion.value = undefined;
118-
}
104+
function showRowDetails({ event, toggleDetails }: RowClickEvent<TrsSearchRow>) {
105+
if ((event.target as Node | undefined)?.nodeName !== "A") {
106+
toggleDetails();
119107
}
120108
}
121109
@@ -126,7 +114,6 @@ function computeItems(items: TrsSearchData[]) {
126114
name: item.name,
127115
description: item.description,
128116
data: item,
129-
_showDetails: false,
130117
};
131118
});
132119
}
@@ -211,76 +198,59 @@ defineExpose({ triggerImport });
211198
<BAlert v-else-if="results.length == 0" variant="info" show>
212199
No search results found, refine your search.
213200
</BAlert>
214-
<BTable
201+
<GTable
215202
v-else
216203
:fields="fields"
217204
:items="itemsComputed"
218205
hover
219206
caption-top
220-
:busy="loading"
221-
tbody-tr-class="clickable-row"
222-
@row-clicked="showRowDetails">
223-
<template v-slot:row-details="row">
207+
clickable-rows
208+
:loading="loading"
209+
@row-click="showRowDetails">
210+
<template v-slot:row-details="{ item }">
224211
<BCard>
225212
<BAlert v-if="importing" variant="info" show>
226213
<LoadingSpan message="Importing workflow" />
227214
</BAlert>
228215

229216
<TrsTool
230-
:trs-tool="row.item.data"
231-
@onImport="(versionId) => onVersionSelected(row.item.data, versionId)"
232-
@onSelect="(versionId) => onVersionSelected(row.item.data, versionId)" />
217+
:trs-tool="item.data"
218+
@onImport="onVersionSelected(item.data, $event)"
219+
@onSelect="onVersionSelected(item.data, $event)" />
233220
</BCard>
234221
</template>
235222

236223
<template v-slot:cell(description)="row">
237224
<span class="trs-description" v-html="renderMarkdown(row.item.data.description)" />
238225
</template>
239-
</BTable>
226+
</GTable>
240227
</div>
241228
</div>
242229
</template>
243230

244231
<style scoped lang="scss">
245-
.trs-description {
246-
position: relative;
247-
overflow: hidden;
248-
display: -webkit-box;
249-
-webkit-box-orient: vertical;
250-
-webkit-line-clamp: 3;
251-
line-clamp: 3;
252-
}
253-
254-
.trs-search-field {
255-
display: flex;
256-
gap: var(--spacing);
257-
align-items: center;
258-
margin-bottom: var(--spacing-4);
259-
260-
:deep(.popper-element) {
261-
max-width: 30vw;
262-
}
263-
}
264-
265232
.vertical-scroll {
266233
max-height: 600px;
267234
overflow-y: auto;
268-
}
269-
.clickable-row:not(.b-table-details) {
270-
cursor: pointer;
271-
}
272-
.clickable-row:not(:first-child) {
273-
border-top: 1px double #ccc;
274-
}
275-
.clickable-row.b-table-has-details {
276-
border: 2px solid var(--brand-primary, #007bff);
277-
border-bottom: none;
278-
}
279-
.clickable-row.b-table-details {
280-
border: 2px solid var(--brand-primary, #007bff);
281-
border-top: none;
282-
}
283-
.clickable-row.b-table-details:hover {
284-
background: unset;
235+
236+
.trs-description {
237+
position: relative;
238+
overflow: hidden;
239+
display: -webkit-box;
240+
-webkit-box-orient: vertical;
241+
-webkit-line-clamp: 3;
242+
line-clamp: 3;
243+
}
244+
245+
.trs-search-field {
246+
display: flex;
247+
gap: var(--spacing);
248+
align-items: center;
249+
margin-bottom: var(--spacing-4);
250+
251+
:deep(.popper-element) {
252+
max-width: 30vw;
253+
}
254+
}
285255
}
286256
</style>

client/src/components/WorkflowInvocationState/JobStep.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const SELECTORS = {
1616
JOB_STATE_BUTTON_NAV: "nav",
1717
JOB_STATE_BUTTON: ".g-button",
1818
JOBS_TABLE: ".job-step-jobs",
19-
JOB_ROW: ".job-step-jobs > tbody > tr",
19+
JOB_ROW: ".job-step-jobs .g-table tbody > tr:not(.g-table-details-row):not(.g-table-empty-row)",
2020
STUBBED_JOB_DETAILS: "anonymous-stub",
2121
};
2222

client/src/components/WorkflowInvocationState/JobStepJobs.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const TEST_NEW_JOB_ID = "sample-job-NEW";
2121

2222
const SELECTORS = {
2323
JOBS_TABLE: ".job-step-jobs",
24-
JOB_ROW: ".job-step-jobs > tbody > tr",
24+
JOB_ROW: ".job-step-jobs .g-table tbody > tr:not(.g-table-details-row):not(.g-table-empty-row)",
2525
JOB_CONTENT: ".g-modal-content",
2626
JOB_INFORMATION_TABLE: "table#job-information",
2727
};

client/src/components/WorkflowInvocationState/JobStepJobs.vue

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
11
<script setup lang="ts">
22
import { faArrowCircleLeft, faArrowCircleRight, faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons";
33
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
4-
import { BTable } from "bootstrap-vue";
54
import { computed, ref, watch } from "vue";
65
76
import type { JobBaseModel } from "@/api/jobs";
8-
9-
import { getJobDuration } from "../JobInformation/utilities";
10-
11-
import GButton from "../BaseComponents/GButton.vue";
12-
import GButtonGroup from "../BaseComponents/GButtonGroup.vue";
13-
import GModal from "../BaseComponents/GModal.vue";
14-
import Heading from "../Common/Heading.vue";
15-
import JobDetails from "../JobInformation/JobDetails.vue";
16-
import JobState from "../JobStates/JobState.vue";
17-
import UtcDate from "../UtcDate.vue";
18-
19-
const props = defineProps<{
7+
import type { TableField } from "@/components/Common/GTable.types";
8+
import { getJobDuration } from "@/components/JobInformation/utilities";
9+
10+
import GButton from "@/components/BaseComponents/GButton.vue";
11+
import GButtonGroup from "@/components/BaseComponents/GButtonGroup.vue";
12+
import GModal from "@/components/BaseComponents/GModal.vue";
13+
import GTable from "@/components/Common/GTable.vue";
14+
import Heading from "@/components/Common/Heading.vue";
15+
import JobDetails from "@/components/JobInformation/JobDetails.vue";
16+
import JobState from "@/components/JobStates/JobState.vue";
17+
import UtcDate from "@/components/UtcDate.vue";
18+
19+
interface Props {
2020
jobs: JobBaseModel[];
2121
invocationId: string;
2222
currentPage: number;
2323
sortDesc: boolean;
2424
perPage: number;
25-
}>();
25+
}
26+
27+
const props = defineProps<Props>();
2628
2729
const emit = defineEmits<{
2830
(e: "update:current-page", value: number): void;
@@ -31,6 +33,14 @@ const emit = defineEmits<{
3133
3234
const showModal = ref(false);
3335
36+
const fields: TableField[] = [
37+
{ key: "id", label: "Job ID" },
38+
{ key: "tool_id", label: "Tool" },
39+
{ key: "update_time", label: "Updated", sortable: true },
40+
{ key: "duration", label: "Time To Finish" },
41+
{ key: "state", label: "State" },
42+
];
43+
3444
/** The job currently being viewed in the modal */
3545
const viewedJob = ref<JobBaseModel | null>(null);
3646
@@ -55,20 +65,13 @@ const viewedJobIndex = computed<number | null>({
5565
},
5666
});
5767
58-
function getTrClass(job: JobBaseModel) {
59-
return {
60-
"clickable-row": true,
61-
"font-weight-bold": job.id === viewedJob.value?.id,
62-
};
63-
}
64-
65-
function jobClicked(job: JobBaseModel) {
66-
viewedJob.value = job;
68+
function jobClicked(event: { item: JobBaseModel }) {
69+
viewedJob.value = event.item;
6770
showModal.value = true;
6871
}
6972
70-
function onSort(sortInfo: { sortDesc: boolean }) {
71-
emit("update:sort-desc", sortInfo.sortDesc);
73+
function onSort(_sortBy: string, sortDesc: boolean) {
74+
emit("update:sort-desc", sortDesc);
7275
}
7376
7477
function navigateJob(direction: "previous" | "next") {
@@ -111,28 +114,24 @@ watch(
111114

112115
<template>
113116
<div>
114-
<BTable
117+
<GTable
118+
clickable-rows
119+
hover
120+
striped
115121
class="job-step-jobs"
116-
primary-key="id"
122+
sort-by="update_time"
117123
:current-page="props.currentPage"
124+
:fields="fields"
118125
:items="props.jobs"
119-
striped
120-
no-sort-reset
121-
no-local-sorting
122-
hover
126+
:local-sorting="false"
123127
:per-page="props.perPage"
124-
:fields="[
125-
{ key: 'id', label: 'Job ID' },
126-
{ key: 'tool_id', label: 'Tool' },
127-
{ key: 'update_time', label: 'Updated', sortable: true },
128-
{ key: 'duration', label: 'Time To Finish' },
129-
{ key: 'state', label: 'State' },
130-
]"
131-
:tbody-tr-class="getTrClass"
132-
@row-clicked="jobClicked"
128+
:sort-desc="props.sortDesc"
129+
@row-click="jobClicked($event)"
133130
@sort-changed="onSort">
134131
<template v-slot:cell(id)="data">
135-
<div class="d-flex flex-gapx-1 align-items-center">
132+
<div
133+
class="d-flex flex-gapx-1 align-items-center"
134+
:class="{ 'font-weight-bold': data.item.id === viewedJob?.id }">
136135
<span>{{ data.item.id }}</span>
137136

138137
<GButton
@@ -158,7 +157,7 @@ watch(
158157
<template v-slot:cell(duration)="data">
159158
{{ getJobDuration(data.item) }}
160159
</template>
161-
</BTable>
160+
</GTable>
162161

163162
<GModal :show.sync="showModal" fixed-height size="medium" @close="viewedJob = null">
164163
<template v-slot:header>
@@ -188,14 +187,15 @@ watch(
188187
</div>
189188
</div>
190189
</template>
190+
191191
<JobDetails v-if="viewedJob" :job-id="viewedJob.id" :invocation-id="invocationId" />
192192
</GModal>
193193
</div>
194194
</template>
195195

196196
<style scoped lang="scss">
197197
.job-step-jobs {
198-
:deep(.clickable-row) {
198+
:deep(.g-table-row-clickable) {
199199
cursor: pointer;
200200
color: var(--color-blue-600);
201201
user-select: text;

0 commit comments

Comments
 (0)