Skip to content

Commit c0e686a

Browse files
authored
Merge pull request #20798 from jmchilton/sample_sheet_history
Implement bare-bones view of sample sheet collections as sheet.
2 parents 7a0bb12 + 8077b89 commit c0e686a

File tree

9 files changed

+252
-49
lines changed

9 files changed

+252
-49
lines changed

client/src/components/Collections/common/CollectionEditView.vue

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { GalaxyApi } from "@/api";
1111
import { updateContentFields } from "@/components/History/model/queries";
1212
import { DatatypesProvider, DbKeyProvider, SuitableConvertersProvider } from "@/components/providers";
1313
import { useConfig } from "@/composables/config";
14+
import { useDetailedCollection } from "@/composables/datasetCollections";
1415
import { useCollectionAttributesStore } from "@/stores/collectionAttributesStore";
15-
import { useCollectionElementsStore } from "@/stores/collectionElementsStore";
1616
import { useHistoryStore } from "@/stores/historyStore";
1717
import localize from "@/utils/localization";
1818
import { prependPath } from "@/utils/redirect";
@@ -40,7 +40,7 @@ const collectionAttributesStore = useCollectionAttributesStore();
4040
const historyStore = useHistoryStore();
4141
const { currentHistoryId } = storeToRefs(historyStore);
4242
43-
const collectionStore = useCollectionElementsStore();
43+
const { collection, collectionLoadError } = useDetailedCollection(props);
4444
4545
const jobError = ref(null);
4646
const errorMessage = ref("");
@@ -63,18 +63,6 @@ const attributesLoadError = computed(() => {
6363
return undefined;
6464
});
6565
66-
const collection = computed(() => {
67-
return collectionStore.getCollectionById(props.collectionId);
68-
});
69-
const collectionLoadError = computed(() => {
70-
if (collection.value) {
71-
const collectionElementLoadError = collectionStore.getLoadingCollectionElementsError(collection.value);
72-
if (collectionElementLoadError) {
73-
return errorMessageAsString(collectionElementLoadError);
74-
}
75-
}
76-
return undefined;
77-
});
7866
watch([attributesLoadError, collectionLoadError], () => {
7967
if (attributesLoadError.value) {
8068
errorMessage.value = attributesLoadError.value;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<script setup lang="ts">
2+
import type { ColDef } from "ag-grid-community";
3+
import { BAlert } from "bootstrap-vue";
4+
import { computed, ref, watch } from "vue";
5+
6+
import type { SampleSheetColumnDefinitions } from "@/api";
7+
import {
8+
type AgRowData,
9+
buildsSampleSheetGrid,
10+
toAgGridColumnDefinition,
11+
} from "@/components/Collections/sheet/useSampleSheetGrid";
12+
import { useDetailedCollection } from "@/composables/datasetCollections";
13+
import { useAgGrid } from "@/composables/useAgGrid";
14+
15+
import LoadingSpan from "@/components/LoadingSpan.vue";
16+
17+
interface Props {
18+
collectionId: string;
19+
}
20+
21+
const props = defineProps<Props>();
22+
23+
const { collection, collectionLoadError } = useDetailedCollection(props);
24+
25+
function initializeRowData(rowData: AgRowData[]) {
26+
const collectionDetailed = collection.value;
27+
if (collectionDetailed) {
28+
for (const element of collectionDetailed.elements) {
29+
const row: AgRowData = { __model_object: element };
30+
(collectionDetailed.column_definitions || []).forEach((colDef) => {
31+
// TODO:
32+
row[colDef.name] = "foobar";
33+
});
34+
rowData.push(row);
35+
}
36+
}
37+
38+
return [];
39+
}
40+
41+
const { rowData, initialize, sampleSheetStyle } = buildsSampleSheetGrid(initializeRowData);
42+
43+
const { gridApi, AgGridVue, onGridReady, theme } = useAgGrid(resize);
44+
45+
function resize() {
46+
if (gridApi.value) {
47+
gridApi.value.sizeColumnsToFit();
48+
}
49+
}
50+
51+
// Generate Column Definitions from Schema
52+
function generateGridColumnDefs(columnDefinitions: SampleSheetColumnDefinitions): ColDef[] {
53+
const columns: ColDef[] = [];
54+
columns.push({
55+
headerName: "Identifier",
56+
field: "__model_object",
57+
editable: false,
58+
cellEditorParams: {},
59+
valueFormatter: (params) => {
60+
return params.data.__model_object.element_identifier;
61+
},
62+
});
63+
(columnDefinitions || []).forEach((colDef) => {
64+
const baseDef = toAgGridColumnDefinition(colDef);
65+
columns.push(baseDef);
66+
});
67+
return columns;
68+
}
69+
70+
// Column Definitions
71+
const columnDefs = computed(() => {
72+
if (!collection.value || !collection.value.column_definitions) {
73+
return [];
74+
}
75+
return generateGridColumnDefs(collection.value.column_definitions as SampleSheetColumnDefinitions);
76+
});
77+
78+
watch(
79+
() => collection.value,
80+
() => {
81+
initialize();
82+
resize();
83+
},
84+
{ immediate: true },
85+
);
86+
87+
// Default Column Properties
88+
const defaultColDef = ref<ColDef>({
89+
editable: false,
90+
sortable: false,
91+
filter: true,
92+
resizable: true,
93+
});
94+
</script>
95+
96+
<template>
97+
<div>
98+
<LoadingSpan v-if="!collection" />
99+
<BAlert v-else-if="collectionLoadError" variant="danger" show dismissible>
100+
{{ collectionLoadError }}
101+
</BAlert>
102+
<div v-else>
103+
<div :class="theme">
104+
<AgGridVue
105+
:row-data="rowData"
106+
:column-defs="columnDefs"
107+
:default-col-def="defaultColDef"
108+
:style="sampleSheetStyle"
109+
@gridReady="onGridReady" />
110+
</div>
111+
</div>
112+
</div>
113+
</template>

client/src/components/Collections/sheet/SampleSheetGrid.vue

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ import { useUploadConfigurations } from "@/composables/uploadConfigurations";
3636
import { useAgGrid } from "@/composables/useAgGrid";
3737
import localize from "@/utils/localization";
3838
39+
import { type AgRowData, buildsSampleSheetGrid, toAgGridColumnDefinition } from "./useSampleSheetGrid";
40+
3941
import UploadSelect from "@/components/Upload//UploadSelect.vue";
4042
import UploadSelectExtension from "@/components/Upload/UploadSelectExtension.vue";
4143
42-
type AgRowData = Record<string, unknown>;
43-
4444
interface Props {
4545
currentHistoryId: string;
4646
collectionType: SampleSheetCollectionType;
@@ -120,14 +120,7 @@ function initializeRowData(rowData: AgRowData[]) {
120120
}
121121
}
122122
123-
// Example Row Data
124-
// const rowData = ref([{ "replicate number": 1, treatment: "treatment1", "is control?": true }]);
125-
const rowData = ref<AgRowData[]>([]);
126-
127-
function initialize() {
128-
rowData.value.splice(0, rowData.value.length);
129-
initializeRowData(rowData.value);
130-
}
123+
const { rowData, initialize, sampleSheetStyle } = buildsSampleSheetGrid(initializeRowData);
131124
132125
const { gridApi, AgGridVue, onGridReady, theme } = useAgGrid(resize);
133126
@@ -232,22 +225,10 @@ function generateGridColumnDefs(columnDefinitions: SampleSheetColumnDefinitions)
232225
}
233226
}
234227
(columnDefinitions || []).forEach((colDef) => {
235-
const headerDescription = colDef.description || colDef.name;
236-
const hasCustomHeaderDescription = headerDescription != colDef.name;
237-
let headerClass = "";
238-
if (hasCustomHeaderDescription) {
239-
headerClass = "ag-grid-column-has-custom-header-description";
240-
}
241-
const baseDef: ColDef = {
242-
headerName: colDef.name,
243-
headerTooltip: colDef.description || colDef.name,
244-
headerClass,
245-
field: colDef.name,
246-
editable: true,
247-
cellEditorParams: {},
248-
valueSetter: (params) => {
249-
return valueSetter(params, colDef);
250-
},
228+
const baseDef = toAgGridColumnDefinition(colDef);
229+
baseDef.editable = true;
230+
baseDef.valueSetter = (params) => {
231+
return valueSetter(params, colDef);
251232
};
252233
253234
// Restrictions: Add dropdown editor for string type with restrictions
@@ -344,10 +325,6 @@ const defaultColDef = ref<ColDef>({
344325
resizable: true,
345326
});
346327
347-
const style = computed(() => {
348-
return { width: "100%", height: "500px" };
349-
});
350-
351328
const emit = defineEmits<{
352329
(e: "workbook-contents", base64Content: string): void;
353330
(e: "on-fetch-target", target: HdcaUploadTarget): void;
@@ -573,6 +550,7 @@ async function attemptCreateViaExistingObjects() {
573550
rows[elementIdentifierFromRow(row)] = elementRow;
574551
}
575552
payload.rows = rows;
553+
payload.column_definitions = props.columnDefinitions;
576554
emit("on-collection-create-payload", payload);
577555
}
578556
@@ -615,7 +593,7 @@ defineExpose({ attemptCreate });
615593
:row-data="rowData"
616594
:column-defs="columnDefs"
617595
:default-col-def="defaultColDef"
618-
:style="style"
596+
:style="sampleSheetStyle"
619597
@gridReady="onGridReady" />
620598
<BRow align-h="center" style="margin-top: 10px">
621599
<BCol v-if="showExtension" cols="4">
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { ColDef } from "ag-grid-community";
2+
import { computed, ref } from "vue";
3+
4+
import type { SampleSheetColumnDefinition } from "@/api";
5+
6+
// Example Row Data
7+
// const rowData = ref([{ "replicate number": 1, treatment: "treatment1", "is control?": true }]);
8+
export type AgRowData = Record<string, unknown>;
9+
10+
export function buildsSampleSheetGrid(initializeRowData: (rowData: AgRowData[]) => void) {
11+
const rowData = ref<AgRowData[]>([]);
12+
13+
const sampleSheetStyle = computed(() => {
14+
return { width: "100%", height: "500px" };
15+
});
16+
17+
function initialize() {
18+
rowData.value.splice(0, rowData.value.length);
19+
initializeRowData(rowData.value);
20+
}
21+
22+
return {
23+
initialize,
24+
rowData,
25+
sampleSheetStyle,
26+
};
27+
}
28+
29+
export function toAgGridColumnDefinition(colDef: SampleSheetColumnDefinition): ColDef {
30+
const headerDescription = colDef.description || colDef.name;
31+
const hasCustomHeaderDescription = headerDescription != colDef.name;
32+
let headerClass = "";
33+
if (hasCustomHeaderDescription) {
34+
headerClass = "ag-grid-column-has-custom-header-description";
35+
}
36+
const baseDef: ColDef = {
37+
headerName: colDef.name,
38+
headerTooltip: colDef.description || colDef.name,
39+
headerClass,
40+
field: colDef.name,
41+
cellEditorParams: {},
42+
};
43+
return baseDef;
44+
}

client/src/components/History/CurrentCollection/CollectionOperations.vue

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { faDownload, faInfoCircle, faRedo } from "@fortawesome/free-solid-svg-icons";
2+
import { faDownload, faInfoCircle, faRedo, faTable } from "@fortawesome/free-solid-svg-icons";
33
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
44
import { computed } from "vue";
55
import { useRouter } from "vue-router/composables";
@@ -22,6 +22,10 @@ const showCollectionDetailsUrl = computed(() =>
2222
);
2323
const disableDownload = props.dsc.populated_state !== "ok";
2424
25+
const sheetUrl = computed(() => {
26+
return `${getAppRoot()}collection/${props.dsc.id}/sheet`;
27+
});
28+
2529
function onDownload() {
2630
window.location.href = downloadUrl.value;
2731
}
@@ -63,6 +67,16 @@ function onDownload() {
6367
<FontAwesomeIcon class="mr-1" :icon="faRedo" />
6468
<span>Run Job Again</span>
6569
</b-button>
70+
<b-button
71+
v-if="sheetUrl"
72+
class="rounded-0 text-decoration-none"
73+
size="sm"
74+
variant="link"
75+
:href="sheetUrl"
76+
@click.prevent.stop="router.push(sheetUrl)">
77+
<FontAwesomeIcon class="mr-1" :icon="faTable" />
78+
<span>View Sheet</span>
79+
</b-button>
6680
</b-button-group>
6781
</nav>
6882
</section>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { computed } from "vue";
2+
3+
import { useCollectionElementsStore } from "@/stores/collectionElementsStore";
4+
import { errorMessageAsString } from "@/utils/simple-error";
5+
6+
interface Props {
7+
collectionId: string;
8+
}
9+
10+
export function useDetailedCollection<T extends Props>(props: T) {
11+
const collectionStore = useCollectionElementsStore();
12+
13+
const collection = computed(() => {
14+
return collectionStore.getDetailedCollectionById(props.collectionId);
15+
});
16+
const collectionLoadError = computed(() => {
17+
if (collection.value) {
18+
const collectionElementLoadError = collectionStore.getLoadingCollectionElementsError(collection.value);
19+
if (collectionElementLoadError) {
20+
return errorMessageAsString(collectionElementLoadError);
21+
}
22+
}
23+
return undefined;
24+
});
25+
return {
26+
collectionStore,
27+
collection,
28+
collectionLoadError,
29+
};
30+
}

client/src/entry/analysis/router.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getGalaxyInstance } from "app";
22
import CitationsList from "components/Citation/CitationsList";
33
import ClientError from "components/ClientError";
44
import CollectionEditView from "components/Collections/common/CollectionEditView";
5+
import DisplayCollectionAsSheet from "components/Collections/common/DisplayCollectionAsSheet";
56
import DatasetList from "components/Dataset/DatasetList";
67
import DatasetView from "components/Dataset/DatasetView";
78
import DatasetDetails from "components/DatasetInformation/DatasetDetails";
@@ -256,6 +257,11 @@ export function getRouter(Galaxy) {
256257
component: CollectionEditView,
257258
props: true,
258259
},
260+
{
261+
path: "collection/:collectionId/sheet",
262+
component: DisplayCollectionAsSheet,
263+
props: true,
264+
},
259265
{
260266
path: "datasets/list",
261267
component: DatasetList,

0 commit comments

Comments
 (0)