Page object permissions summary...

This commit is contained in:
John Chilton
2024-04-16 12:14:09 -04:00
parent 0a68b5df25
commit bf57676e9e
14 changed files with 693 additions and 8 deletions
+11 -2
View File
@@ -1,4 +1,4 @@
import { CollectionEntry, DCESummary, HDCADetailed, isHDCA } from "@/api";
import { CollectionEntry, DCESummary, HDCADetailed, HDCASummary, isHDCA } from "@/api";
import { fetcher } from "@/api/schema";
const DEFAULT_LIMIT = 50;
@@ -11,7 +11,16 @@ const getCollectionDetails = fetcher.path("/api/dataset_collections/{id}").metho
*/
export async function fetchCollectionDetails(params: { id: string }): Promise<HDCADetailed> {
const { data } = await getCollectionDetails({ id: params.id });
return data;
return data as HDCADetailed;
}
/**
* Fetches the details of a collection.
* @param params.id The ID of the collection (HDCA) to fetch.
*/
export async function fetchCollectionSummary(params: { id: string }): Promise<HDCASummary> {
const { data } = await getCollectionDetails({ id: params.id, view: "collection" });
return data as HDCASummary;
}
const getCollectionContents = fetcher
+2
View File
@@ -12,3 +12,5 @@ export const updateHistoryItemsInBulk = fetcher
.path("/api/histories/{history_id}/contents/bulk")
.method("put")
.create();
export const sharing = fetcher.path("/api/histories/{history_id}/sharing").method("get").create();
export const enableLink = fetcher.path("/api/histories/{history_id}/enable_link_access").method("put").create();
+2
View File
@@ -2,6 +2,8 @@ import { components, fetcher } from "@/api/schema";
export type JobDestinationParams = components["schemas"]["JobDestinationParams"];
export const getJobDetails = fetcher.path("/api/jobs/{job_id}").method("get").create();
export const jobLockStatus = fetcher.path("/api/job_lock").method("get").create();
export const jobLockUpdate = fetcher.path("/api/job_lock").method("put").create();
+3
View File
@@ -3,3 +3,6 @@ import { fetcher } from "@/api/schema";
export const workflowsFetcher = fetcher.path("/api/workflows").method("get").create();
export const invocationCountsFetcher = fetcher.path("/api/workflows/{workflow_id}/counts").method("get").create();
export const sharing = fetcher.path("/api/workflows/{workflow_id}/sharing").method("get").create();
export const enableLink = fetcher.path("/api/workflows/{workflow_id}/enable_link_access").method("put").create();
+45
View File
@@ -168,3 +168,48 @@ function namedArgumentRegex(argument: string): RegExp {
function escapeRegExpReplacement(value: string): string {
return value.replace(/\$/g, "$$$$");
}
class ReferencedObjects {
jobs: Set<string> = new Set();
historyDatasets: Set<string> = new Set();
historyDatasetCollections: Set<string> = new Set();
workflows: Set<string> = new Set();
invocations: Set<string> = new Set();
}
export function referencedObjects(markdown: string) {
const { sections } = splitMarkdown(markdown);
const objects = new ReferencedObjects();
for (const section of sections) {
if (!("args" in section)) {
continue;
}
const args = section.args;
if (!args) {
continue;
}
if ("job_id" in args) {
addToSetIfHasValue(args.job_id, objects.jobs);
}
if ("history_dataset_id" in args) {
addToSetIfHasValue(args.history_dataset_id, objects.historyDatasets);
}
if ("history_dataset_collection_id" in args) {
addToSetIfHasValue(args.history_dataset_collection_id, objects.historyDatasetCollections);
}
if ("invocation_id" in args) {
addToSetIfHasValue(args.invocation_id, objects.invocations);
}
if ("workflow_id" in args) {
addToSetIfHasValue(args.workflow_id, objects.workflows);
}
// TODO: implicit collect job ids
}
return objects;
}
function addToSetIfHasValue(value: string | undefined, toSet: Set<string>): void {
if (value) {
toSet.add(value);
}
}
@@ -0,0 +1,311 @@
<script setup lang="ts">
import axios from "axios";
import Vue, { computed, Ref, ref, watch } from "vue";
import { fetchCollectionSummary } from "@/api/datasetCollections";
import { enableLink, sharing } from "@/api/histories";
import { fetchInvocationDetails } from "@/api/invocations";
import { getJobDetails } from "@/api/jobs";
import { enableLink as enableLinkWorkflow, sharing as sharingWorkflow } from "@/api/workflows";
import { useToast } from "@/composables/toast";
import { useDatasetStore } from "@/stores/datasetStore";
import { useHistoryStore } from "@/stores/historyStore";
import { useWorkflowStore } from "@/stores/workflowStore";
import _l from "@/utils/localization";
import { withPrefix } from "@/utils/redirect";
import { errorMessageAsString } from "@/utils/simple-error";
import {
initializeObjectReferences,
initializeObjectToHistoryRefs,
updateReferences,
} from "./object-permission-composables";
import PermissionObjectType from "./PermissionObjectType.vue";
import SharingIndicator from "./SharingIndicator.vue";
import LoadingSpan from "@/components/LoadingSpan.vue";
const { getHistoryNameById, loadHistoryById } = useHistoryStore();
const { getStoredWorkflowNameByInstanceId, fetchWorkflowForInstanceId } = useWorkflowStore();
const { getDataset, fetchDataset } = useDatasetStore();
const toast = useToast();
interface ObjectPermissionsProps {
markdownContent: string;
}
const props = defineProps<ObjectPermissionsProps>();
const referencedObjects = initializeObjectReferences();
const {
referencedJobIds,
referencedHistoryDatasetIds,
referencedHistoryDatasetCollectionIds,
referencedWorkflowIds,
referencedInvocationIds,
} = referencedObjects;
// We mostly defer to history permissions for various objects. Track them and merge
// into histories IDs.
const { jobsToHistories, invocationsToHistories, historyDatasetCollectionsToHistories, historyIds } =
initializeObjectToHistoryRefs(referencedObjects);
type ErrorString = string;
type AccessibleState = Boolean | null | ErrorString;
type AccessibleMapRef = Ref<{ [key: string]: AccessibleState }>;
const historyAccessible: AccessibleMapRef = ref({});
const workflowAccessible: AccessibleMapRef = ref({});
const historyDatasetAccessible: AccessibleMapRef = ref({});
function catchErrorToToast(title: string, prolog: string) {
function handleError(e: Error) {
toast.error(`${prolog} Reason: ${errorMessageAsString(e)}.`, title);
}
return handleError;
}
watch(referencedJobIds, async () => {
referencedJobIds.value.forEach((jobId) => {
if (jobId in jobsToHistories.value) {
return;
}
const handleError = catchErrorToToast(
"Failed to job information",
"Some referenced objects may not be listed."
);
getJobDetails({ job_id: jobId })
.then(({ data }) => {
if ("history_id" in data) {
const historyId = data.history_id;
Vue.set(jobsToHistories.value, jobId, historyId);
}
})
.catch(handleError);
});
});
watch(referencedInvocationIds, async () => {
referencedInvocationIds.value.forEach((invocationId) => {
if (invocationId in invocationsToHistories.value) {
return;
}
const handleError = catchErrorToToast(
"Failed to fetch workflow information",
"Some referenced objects may not be listed."
);
fetchInvocationDetails({ id: invocationId })
.then(({ data }) => {
if ("history_id" in data) {
const historyId = data.history_id;
Vue.set(invocationsToHistories.value, invocationId, historyId);
}
})
.catch(handleError);
});
});
watch(referencedHistoryDatasetCollectionIds, async () => {
referencedHistoryDatasetCollectionIds.value.forEach((historyDatasetCollectionId) => {
if (historyDatasetCollectionId in historyDatasetCollectionsToHistories.value) {
return;
}
const handleError = catchErrorToToast(
"Failed to fetch collection information",
"Some referenced objects may not be listed."
);
fetchCollectionSummary({ id: historyDatasetCollectionId })
.then((data) => {
const historyId = data.history_id;
Vue.set(historyDatasetCollectionsToHistories.value, historyDatasetCollectionId, historyId);
})
.catch(handleError);
});
});
interface ItemInterface {
id: string;
accessible: Boolean | null;
name: string;
type: string;
}
const histories = computed<ItemInterface[]>(() => {
return historyIds.value.map((historyId: string) => {
return {
id: historyId,
type: "history",
name: getHistoryNameById(historyId),
accessible: historyAccessible.value[historyId],
} as ItemInterface;
});
});
const workflows = computed<ItemInterface[]>(() => {
return referencedWorkflowIds.value.map((workflowId: string) => {
return {
id: workflowId,
type: "workflow",
name: getStoredWorkflowNameByInstanceId(workflowId),
accessible: workflowAccessible.value[workflowId],
} as ItemInterface;
});
});
const datasets = computed<ItemInterface[]>(() => {
return referencedHistoryDatasetIds.value.map((historyDatasetId: string) => {
return {
id: historyDatasetId,
type: "historyDataset",
name: getDataset(historyDatasetId)?.name || "Fetching dataset name...",
accessible: historyDatasetAccessible.value[historyDatasetId],
} as ItemInterface;
});
});
const loading = ref(false);
const SHARING_FIELD = { key: "accessible", label: _l("Accessible"), sortable: false, thStyle: { width: "10%" } };
const NAME_FIELD = { key: "name", label: _l("Name"), sortable: true };
const TYPE_FIELD = { key: "type", label: _l("Type"), sortable: true, thStyle: { width: "10%" } };
const tableFields = [SHARING_FIELD, TYPE_FIELD, NAME_FIELD];
watch(
props,
() => {
updateReferences(referencedObjects, props.markdownContent);
initWorkflowData();
initHistoryDatasetData();
},
{ immediate: true }
);
watch(historyIds, () => {
for (const historyId of historyIds.value) {
loadHistoryById(historyId);
if (historyId && !(historyId in historyAccessible.value)) {
Vue.set(historyAccessible.value, historyId, null);
sharing({ history_id: historyId })
.then((response) => {
const accessible = response.data.importable;
Vue.set(historyAccessible.value, historyId, accessible);
})
.catch((e) => {
const errorMessage = errorMessageAsString(e);
const title = "Failed to fetch history metadata.";
toast.error(errorMessage, title);
Vue.set(historyAccessible.value, historyId, `${title} Reason: ${errorMessage}.`);
});
}
}
});
function initWorkflowData() {
for (const workflowId of referencedWorkflowIds.value) {
fetchWorkflowForInstanceId(workflowId);
if (workflowId && !(workflowId in workflowAccessible.value)) {
Vue.set(workflowAccessible.value, workflowId, null);
sharingWorkflow({ workflow_id: workflowId })
.then((response) => {
const accessible = response.data.importable;
Vue.set(workflowAccessible.value, workflowId, accessible);
})
.catch((e) => {
const errorMessage = errorMessageAsString(e);
const title = "Failed to fetch workflow metadata.";
toast.error(errorMessage, title);
Vue.set(workflowAccessible.value, workflowId, `${title} Reason: ${errorMessage}.`);
});
}
}
}
function initHistoryDatasetData() {
for (const historyDatasetId of referencedHistoryDatasetIds.value) {
fetchDataset({ id: historyDatasetId });
if (historyDatasetId && !(historyDatasetId in historyDatasetAccessible.value)) {
axios
.get(withPrefix(`/dataset/get_edit?dataset_id=${historyDatasetId}`))
.then((response) => {
const permissionDisable = response.data.permission_disable;
const permissionInputs = response.data.permission_inputs;
if (permissionDisable) {
const errorStr = `Cannot modify permissions of this dataset. Reason: ${permissionInputs[0].label}`;
Vue.set(historyDatasetAccessible.value, historyDatasetId, errorStr);
return;
}
const accessPermissionInput = permissionInputs[1];
if (accessPermissionInput.name != "DATASET_ACCESS") {
throw Error("Galaxy Bug");
}
const accessible = (accessPermissionInput.value || []).length == 0;
Vue.set(historyDatasetAccessible.value, historyDatasetId, accessible);
})
.catch((e) => {
const errorMessage = errorMessageAsString(e);
const title = "Failed to fetch dataset metadata.";
toast.error(errorMessage, title);
Vue.set(historyDatasetAccessible.value, historyDatasetId, `${title} Reason: ${errorMessage}.`);
});
}
}
}
const tableItems = computed<ItemInterface[]>(() => {
return [...histories.value, ...workflows.value, ...datasets.value];
});
function makeAccessible(item: ItemInterface) {
let promise;
let accessibleMap: AccessibleMapRef;
if (item.type == "history") {
promise = enableLink({ history_id: item.id });
accessibleMap = historyAccessible;
} else if (item.type == "workflow") {
promise = enableLinkWorkflow({ workflow_id: item.id });
accessibleMap = workflowAccessible;
} else if (item.type == "historyDataset") {
const data = {
dataset_id: item.id,
action: "remove_restrictions",
};
promise = axios.put(withPrefix(`/api/datasets/${item.id}/permissions`), data);
accessibleMap = historyDatasetAccessible;
}
if (!promise) {
console.log("Serious client programming error - unknown object type encountered.");
return;
}
promise
.then(() => Vue.set(accessibleMap.value, item.id, true))
.catch((e) => {
const errorMessage = errorMessageAsString(e);
const title = "Failed update object accessibility.";
toast.error(errorMessage, title);
Vue.set(accessibleMap.value, item.id, `${title} Reason: ${errorMessage}.`);
});
}
</script>
<template>
<div>
<b-table :items="tableItems" :fields="tableFields">
<template v-slot:empty>
<LoadingSpan v-if="loading" message="Loading objects" />
<b-alert v-else variant="info" show>
<div>No objects found in referenced Galaxy markdown content.</div>
</b-alert>
</template>
<template v-slot:cell(name)="row">
{{ row.item.name }}
</template>
<template v-slot:cell(accessible)="row">
<SharingIndicator :accessible="row.item.accessible" @makeAccessible="makeAccessible(row.item)" />
</template>
<template v-slot:cell(type)="row">
<PermissionObjectType :type="row.item.type" />
</template>
</b-table>
</div>
</template>
@@ -0,0 +1,15 @@
<script setup lang="ts">
import ObjectPermissions from "./ObjectPermissions.vue";
interface ObjectPermissionsProps {
markdownContent: string;
}
defineProps<ObjectPermissionsProps>();
</script>
<template>
<b-modal v-bind="$attrs" title="Page Object Permissions" title-tag="h2" ok-only v-on="$listeners">
<ObjectPermissions :markdown-content="markdownContent" />
</b-modal>
</template>
@@ -6,6 +6,20 @@
mode="page"
@onUpdate="onUpdate">
<template v-slot:buttons>
<ObjectPermissionsModal
id="object-permissions-modal"
v-model="showPermissions"
:markdown-content="markdownText" />
<b-button
id="permissions-button"
v-b-tooltip.hover.bottom
v-b-modal:object-permissions-modal
title="Permissions"
variant="link"
role="button"
@click="showPermissions = true">
<FontAwesomeIcon icon="users" />
</b-button>
<b-button
id="save-button"
v-b-tooltip.hover.bottom
@@ -30,7 +44,7 @@
<script>
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye, faSave } from "@fortawesome/free-solid-svg-icons";
import { faEye, faSave, faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import BootstrapVue from "bootstrap-vue";
import MarkdownEditor from "components/Markdown/MarkdownEditor";
@@ -39,14 +53,17 @@ import Vue from "vue";
import { save } from "./util";
import ObjectPermissionsModal from "./ObjectPermissionsModal.vue";
Vue.use(BootstrapVue);
library.add(faEye, faSave);
library.add(faEye, faSave, faUsers);
export default {
components: {
MarkdownEditor,
FontAwesomeIcon,
ObjectPermissionsModal,
},
props: {
pageId: {
@@ -73,6 +90,7 @@ export default {
data: function () {
return {
markdownText: this.content,
showPermissions: false,
};
},
methods: {
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed } from "vue";
interface PermissionObjectTypeProps {
type: "history" | "historyDataset" | "workflow";
}
const props = defineProps<PermissionObjectTypeProps>();
const typeTitle = computed(() => {
if (props.type == "history") {
return "History";
} else if (props.type == "historyDataset") {
return "Dataset";
} else {
return "Workflow";
}
});
</script>
<template>
<span class="object-type">
{{ typeTitle }}
</span>
</template>
<style scoped>
.object-type {
text-decoration: underline;
}
</style>
@@ -0,0 +1,70 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import LoadingSpan from "@/components/LoadingSpan.vue";
interface SharingIndicatorProps {
accessible: Boolean | String | null;
}
const emit = defineEmits<{
(e: "makeAccessible"): void;
}>();
const props = defineProps<SharingIndicatorProps>();
const makingAccessible = ref(false);
function makeAccessible() {
makingAccessible.value = true;
emit("makeAccessible");
}
watch(props, () => {
makingAccessible.value = false;
});
</script>
<template>
<div>
<LoadingSpan v-if="accessible == null || makingAccessible" spinner-only> </LoadingSpan>
<div v-else-if="typeof accessible == 'string'">
<Icon
:title="accessible"
icon="exclamation-triangle"
class="make-page-object-accessible-sharing-error-icon" />
</div>
<BFormCheckbox
v-else
switch
:checked="accessible"
class="make-page-object-accessible"
:disabled="accessible"
@change="
(event) => {
makeAccessible(event);
}
">
</BFormCheckbox>
</div>
</template>
<style lang="scss">
/* scoped doesn't seem to work to prefixing with a class name */
@import "theme/blue.scss";
.make-page-object-accessible-sharing-error-icon {
color: $brand-danger;
}
.make-page-object-accessible .custom-control-label::after {
background-color: white;
}
.make-page-object-accessible .custom-control-label::before {
background-color: lighten($brand-warning, 15%) !important;
}
.make-page-object-accessible .custom-control-input[disabled] ~ .custom-control-label::before {
background-color: lighten($brand-success, 15%) !important;
}
</style>
@@ -0,0 +1,81 @@
import { set } from "vue";
import {
initializeObjectReferences,
initializeObjectToHistoryRefs,
updateReferences,
} from "./object-permission-composables";
describe("object-permission-composables", () => {
it("should initialize and update refs in a markdown document", () => {
const refs = initializeObjectReferences();
expect(refs.referencedJobIds.value.length).toBe(0);
updateReferences(refs, "some content\n```galaxy\njob_metrics(job_id=THISFAKEID)\n```\nfoo bar\n");
expect(refs.referencedJobIds.value.length).toBe(1);
expect(refs.referencedJobIds.value[0]).toBe("THISFAKEID");
});
describe("initializeObjectToHistoryRefs", () => {
function init() {
const refs = initializeObjectReferences();
const historyMaps = initializeObjectToHistoryRefs(refs);
return { refs, historyMaps };
}
describe("historyIds computed", () => {
it("should merge in referenced job histories once they've been cached", async () => {
const { refs, historyMaps } = init();
refs.referencedJobIds.value = ["THISFAKEID"];
expect(historyMaps.historyIds.value.length).toBe(0);
set(historyMaps.jobsToHistories.value, "THISFAKEID", "THATFAKEID");
expect(historyMaps.historyIds.value.length).toBe(1);
expect(historyMaps.historyIds.value[0]).toBe("THATFAKEID");
});
it("should merge in referenced invocation histories once they've been cached", async () => {
const { refs, historyMaps } = init();
refs.referencedInvocationIds.value = ["THISFAKEID"];
expect(historyMaps.historyIds.value.length).toBe(0);
set(historyMaps.invocationsToHistories.value, "THISFAKEID", "THATFAKEID");
expect(historyMaps.historyIds.value.length).toBe(1);
expect(historyMaps.historyIds.value[0]).toBe("THATFAKEID");
});
it("should merge in referenced collections once they've been cached", async () => {
const { refs, historyMaps } = init();
refs.referencedHistoryDatasetCollectionIds.value = ["THISFAKEID"];
expect(historyMaps.historyIds.value.length).toBe(0);
set(historyMaps.historyDatasetCollectionsToHistories.value, "THISFAKEID", "THATFAKEID");
expect(historyMaps.historyIds.value.length).toBe(1);
expect(historyMaps.historyIds.value[0]).toBe("THATFAKEID");
});
it("should merge in referenced objects across sources once they've been cached", async () => {
const { refs, historyMaps } = init();
refs.referencedJobIds.value = ["THISFAKEJOBID"];
refs.referencedInvocationIds.value = ["THISFAKEINVOCATIONID"];
refs.referencedHistoryDatasetCollectionIds.value = ["THISFAKECOLLECTIONID"];
set(historyMaps.jobsToHistories.value, "THISFAKEJOBID", "HISTORYID1");
set(historyMaps.invocationsToHistories.value, "THISFAKEINVOCATIONID", "HISTORYID2");
set(historyMaps.historyDatasetCollectionsToHistories.value, "THISFAKECOLLECTIONID", "HISTORYID3");
expect(historyMaps.historyIds.value.length).toBe(3);
expect(historyMaps.historyIds.value).toContain("HISTORYID1");
expect(historyMaps.historyIds.value).toContain("HISTORYID2");
expect(historyMaps.historyIds.value).toContain("HISTORYID3");
});
it("should merge in referenced objects across sources once they've been cached and de-duplicate", async () => {
const { refs, historyMaps } = init();
refs.referencedJobIds.value = ["THISFAKEJOBID"];
refs.referencedInvocationIds.value = ["THISFAKEINVOCATIONID"];
refs.referencedHistoryDatasetCollectionIds.value = ["THISFAKECOLLECTIONID"];
set(historyMaps.jobsToHistories.value, "THISFAKEJOBID", "THATFAKEID");
set(historyMaps.invocationsToHistories.value, "THISFAKEINVOCATIONID", "THATFAKEID");
set(historyMaps.historyDatasetCollectionsToHistories.value, "THISFAKECOLLECTIONID", "THATFAKEID");
expect(historyMaps.historyIds.value.length).toBe(1);
expect(historyMaps.historyIds.value[0]).toBe("THATFAKEID");
});
});
});
});
@@ -0,0 +1,89 @@
import { computed, Ref, ref } from "vue";
import { referencedObjects } from "@/components/Markdown/parse";
interface ObjectVueRefs {
referencedJobIds: Ref<string[]>;
referencedHistoryDatasetIds: Ref<string[]>;
referencedHistoryDatasetCollectionIds: Ref<string[]>;
referencedWorkflowIds: Ref<string[]>;
referencedInvocationIds: Ref<string[]>;
}
export function initializeObjectReferences(): ObjectVueRefs {
const referencedJobIds = ref<string[]>([]);
const referencedHistoryDatasetIds = ref<string[]>([]);
const referencedHistoryDatasetCollectionIds = ref<string[]>([]);
const referencedWorkflowIds = ref<string[]>([]);
const referencedInvocationIds = ref<string[]>([]);
return {
referencedJobIds,
referencedHistoryDatasetIds,
referencedHistoryDatasetCollectionIds,
referencedWorkflowIds,
referencedInvocationIds,
};
}
export function updateReferences(objectsRefs: ObjectVueRefs, markdown: string) {
const {
referencedJobIds,
referencedHistoryDatasetIds,
referencedHistoryDatasetCollectionIds,
referencedWorkflowIds,
referencedInvocationIds,
} = objectsRefs;
const objects = referencedObjects(markdown);
referencedJobIds.value = Array.from(objects.jobs.values());
referencedHistoryDatasetIds.value = Array.from(objects.historyDatasets.values());
referencedHistoryDatasetCollectionIds.value = Array.from(objects.historyDatasetCollections.values());
referencedWorkflowIds.value = Array.from(objects.workflows.values());
referencedInvocationIds.value = Array.from(objects.invocations.values());
}
export function initializeObjectToHistoryRefs(referenceObjects: ObjectVueRefs) {
const jobsToHistories: Ref<{ [key: string]: string }> = ref({});
const invocationsToHistories: Ref<{ [key: string]: string }> = ref({});
const historyDatasetCollectionsToHistories: Ref<{ [key: string]: string }> = ref({});
const historyIds = computed<string[]>(() => {
// be sure to reference all refs required for full computation
const jobIds = referenceObjects.referencedJobIds.value;
const jobMapping = jobsToHistories.value;
const invocationIds = referenceObjects.referencedInvocationIds.value;
const invocationMapping = invocationsToHistories.value;
const collectionIds = referenceObjects.referencedHistoryDatasetCollectionIds.value;
const collectionMapping = historyDatasetCollectionsToHistories.value;
const theHistories = new Set();
for (const jobId of jobIds) {
if (jobId in jobMapping) {
theHistories.add(jobMapping[jobId]);
}
}
for (const invocationId of invocationIds) {
if (invocationId in invocationMapping) {
theHistories.add(invocationMapping[invocationId]);
}
}
for (const historyDatasetCollectionId of collectionIds) {
if (historyDatasetCollectionId in collectionMapping) {
theHistories.add(collectionMapping[historyDatasetCollectionId]);
}
}
const historyIds = Array.from(theHistories.values()) as string[];
return historyIds;
});
return {
jobsToHistories,
invocationsToHistories,
historyDatasetCollectionsToHistories,
historyIds,
};
}
@@ -11,6 +11,7 @@ from typing_extensions import Annotated
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.schema.fields import DecodedDatabaseIdField
from galaxy.schema.schema import (
AnyHDCA,
CreateNewCollectionPayload,
DatasetCollectionInstanceType,
DCESummary,
@@ -43,6 +44,11 @@ InstanceTypeQueryParam: DatasetCollectionInstanceType = Query(
description="The type of collection instance. Either `history` (default) or `library`.",
)
ViewTypeQueryParam: str = Query(
default="element",
description="The view of collection instance to return.",
)
@router.cbv
class FastAPIDatasetCollections:
@@ -104,8 +110,9 @@ class FastAPIDatasetCollections:
id: HistoryHDCAIDPathParam,
trans: ProvidesHistoryContext = DependsOnTrans,
instance_type: DatasetCollectionInstanceType = InstanceTypeQueryParam,
) -> HDCADetailed:
return self.service.show(trans, id, instance_type)
view: str = ViewTypeQueryParam,
) -> AnyHDCA:
return self.service.show(trans, id, instance_type, view=view)
@router.get(
"/api/dataset_collections/{hdca_id}/contents/{parent_id}",
@@ -32,6 +32,7 @@ from galaxy.schema.fields import (
ModelClassField,
)
from galaxy.schema.schema import (
AnyHDCA,
CreateNewCollectionPayload,
DatasetCollectionInstanceType,
DCESummary,
@@ -184,7 +185,8 @@ class DatasetCollectionsService(ServiceBase, UsesLibraryMixinItems):
trans: ProvidesHistoryContext,
id: DecodedDatabaseIdField,
instance_type: DatasetCollectionInstanceType = "history",
) -> HDCADetailed:
view: str = "element",
) -> AnyHDCA:
"""
Returns information about a particular dataset collection.
"""
@@ -203,7 +205,7 @@ class DatasetCollectionsService(ServiceBase, UsesLibraryMixinItems):
security=trans.security,
url_builder=trans.url_builder,
parent=parent,
view="element",
view=view,
)
return rval