Skip to content

Commit

Permalink
Page object permissions summary...
Browse files Browse the repository at this point in the history
  • Loading branch information
jmchilton committed Dec 22, 2023
1 parent 8e84f7c commit 1d78c78
Show file tree
Hide file tree
Showing 10 changed files with 458 additions and 6 deletions.
3 changes: 3 additions & 0 deletions client/src/api/histories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ export const historiesFetcher = fetcher.path("/api/histories").method("get").cre
export const archivedHistoriesFetcher = fetcher.path("/api/histories/archived").method("get").create();
export const undeleteHistory = fetcher.path("/api/histories/deleted/{history_id}/undelete").method("post").create();
export const purgeHistory = fetcher.path("/api/histories/{history_id}").method("delete").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();
4 changes: 4 additions & 0 deletions client/src/api/workflows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { fetcher } from "@/api/schema";

export const sharing = fetcher.path("/api/workflows/{id}/sharing").method("get").create();
export const enableLink = fetcher.path("/api/workflows/{id}/enable_link_access").method("put").create();
45 changes: 45 additions & 0 deletions client/src/components/Markdown/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,48 @@ export function getArgs(content: string) {
content: content,
};
}

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, toSet: Set<string>): void {
if (value) {
toSet.add(value);
}
}
267 changes: 267 additions & 0 deletions client/src/components/PageEditor/ObjectPermissions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
<script setup lang="ts">
import axios from "axios";
import Vue, { computed, Ref, ref, watch } from "vue";
import { enableLink, sharing } from "@/api/histories";
import { enableLink as enableLinkWorkflow, sharing as sharingWorkflow } from "@/api/workflows";
import { referencedObjects } from "@/components/Markdown/parse";
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 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();
interface ObjectPermissionsProps {
markdownContent: string;
}
const props = defineProps<ObjectPermissionsProps>();
const referencedJobIds = ref<string[]>([]);
const referencedHistoryDatasetIds = ref<string[]>([]);
const referencedHistoryDatasetCollectionIds = ref<string[]>([]);
const referencedWorkflowIds = ref<string[]>([]);
const referencedInvocationIds = ref<string[]>([]);
// We mostly defer to history permissions for all these objects. Track them...
const jobsToHistories: Ref<{ [key: string]: string }> = ref({});
const invocationsToHistories: Ref<{ [key: string]: string }> = ref({});
const historyDatasetCollectionsToHistories: Ref<{ [key: string]: string }> = ref({});
const historyAccessible: Ref<{ [key: string]: Boolean | null }> = ref({});
const workflowAccessible: Ref<{ [key: string]: Boolean | null }> = ref({});
const historyDatasetAccessible: Ref<{ [key: string]: Boolean | null }> = ref({});
watch(referencedJobIds, async () => {
referencedJobIds.value.forEach((jobId) => {
if (jobId in jobsToHistories.value) {
return;
}
axios.get(withPrefix(`/api/jobs/${jobId}`)).then((response) => {
const historyId = response.data.history_id;
Vue.set(jobsToHistories.value, jobId, historyId);
});
});
});
watch(referencedInvocationIds, async () => {
referencedInvocationIds.value.forEach((invocationId) => {
if (invocationId in invocationsToHistories.value) {
return;
}
axios.get(withPrefix(`/api/invocations/${invocationId}`)).then((response) => {
const historyId = response.data.history_id;
console.log(`${invocationId} => ${historyId}`);
Vue.set(invocationsToHistories.value, invocationId, historyId);
});
});
});
watch(referencedHistoryDatasetCollectionIds, async () => {
referencedHistoryDatasetCollectionIds.value.forEach((historyDatasetCollectionId) => {
if (historyDatasetCollectionId in historyDatasetCollectionsToHistories.value) {
return;
}
axios.get(withPrefix(`/api/invocations/${historyDatasetCollectionId}`)).then((response) => {
const historyId = response.data.history_id;
console.log(`${historyDatasetCollectionId} => ${historyId}`);
Vue.set(historyDatasetCollectionsToHistories.value, historyDatasetCollectionId, historyId);
});
});
});
const historyIds = computed<string[]>(() => {
// be sure to reference all refs required for full computation
const jobIds = referencedJobIds.value;
const jobMapping = jobsToHistories.value;
const invocationIds = referencedInvocationIds.value;
const invocationMapping = invocationsToHistories.value;
const collectionIds = 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;
});
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,
() => {
const objects = referencedObjects(props.markdownContent);
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());
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);
});
}
}
});
function initWorkflowData() {
for (const workflowId of referencedWorkflowIds.value) {
console.log(`workflow id is ${workflowId}`);
fetchWorkflowForInstanceId(workflowId);
if (workflowId && !(workflowId in workflowAccessible.value)) {
Vue.set(workflowAccessible.value, workflowId, null);
sharingWorkflow({ id: workflowId }).then((response) => {
const accessible = response.data.importable;
Vue.set(workflowAccessible.value, workflowId, accessible);
});
}
}
}
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 permissionInputs = response.data.permission_inputs;
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);
});
}
}
}
const tableItems = computed<ItemInterface[]>(() => {
return [...histories.value, ...workflows.value, ...datasets.value];
});
function makeAccessible(item: ItemInterface) {
if (item.type == "history") {
enableLink({ history_id: item.id }).then((response) => {
Vue.set(historyAccessible.value, item.id, true);
});
} else if (item.type == "workflow") {
enableLinkWorkflow({ id: item.id }).then((response) => {
Vue.set(workflowAccessible.value, item.id, true);
});
} else if (item.type == "historyDataset") {
const data = {
dataset_id: item.id,
action: "remove_restrictions",
};
axios.put(withPrefix(`/api/datasets/${item.id}/permissions`), data).then((response) => {
console.log(response);
Vue.set(historyDatasetAccessible.value, item.id, true);
});
}
}
</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>
15 changes: 15 additions & 0 deletions client/src/components/PageEditor/ObjectPermissionsModal.vue
Original file line number Diff line number Diff line change
@@ -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>
Loading

0 comments on commit 1d78c78

Please sign in to comment.