Skip to content

Commit

Permalink
Implement workflow landings.
Browse files Browse the repository at this point in the history
Includes a lot of plumbing for tools but not the APIs - they are not ready to go yet.
  • Loading branch information
jmchilton committed Sep 24, 2024
1 parent 851b87d commit dcc301e
Show file tree
Hide file tree
Showing 24 changed files with 916 additions and 16 deletions.
4 changes: 4 additions & 0 deletions client/src/api/landings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { type components } from "@/api/schema";

export type ClaimLandingPayload = components["schemas"]["ClaimLandingPayload"];
export type WorkflowLandingRequest = components["schemas"]["WorkflowLandingRequest"];
66 changes: 66 additions & 0 deletions client/src/components/Form/Elements/FormData/FormDataUri.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<template>
<b-row align-v="center">
<b-col>
<b-form-input :id="id" v-model="displayValue" :class="['ui-input', cls]" :readonly="true" />
</b-col>
</b-row>
</template>

<script>
export default {
props: {
value: {
type: Object,
},
id: {
type: String,
default: "",
},
multiple: {
type: Boolean,
default: false,
},
cls: {
// Refers to an optional custom css class name
type: String,
default: null,
},
},
computed: {
displayValue: {
get() {
return this.value.url;
},
set(newVal, oldValue) {
if (newVal !== this.value.url) {
this.$emit("input", this.value);
}
},
},
currentValue: {
get() {
const v = this.value ?? "";
if (Array.isArray(v)) {
if (v.length === 0) {
return "";
}
return this.multiple
? this.value.reduce((str_value, v) => str_value + String(v) + "\n", "")
: String(this.value[0]);
}
return String(v);
},
set(newVal, oldVal) {
if (newVal !== oldVal) {
this.$emit("input", newVal);
}
},
},
},
};
</script>
<style scoped>
.ui-input-linked {
border-left-width: 0.5rem;
}
</style>
15 changes: 15 additions & 0 deletions client/src/components/Form/FormElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { FormParameterAttributes, FormParameterTypes, FormParameterValue }
import FormBoolean from "./Elements/FormBoolean.vue";
import FormColor from "./Elements/FormColor.vue";
import FormData from "./Elements/FormData/FormData.vue";
import FormDataUri from "./Elements/FormData/FormDataUri.vue";
import FormDataDialog from "./Elements/FormDataDialog.vue";
import FormDirectory from "./Elements/FormDirectory.vue";
import FormDrilldown from "./Elements/FormDrilldown/FormDrilldown.vue";
Expand Down Expand Up @@ -130,6 +131,14 @@ const elementId = computed(() => `form-element-${props.id}`);
const hasAlert = computed(() => alerts.value.length > 0);
const showPreview = computed(() => (collapsed.value && attrs.value["collapsible_preview"]) || props.disabled);
const showField = computed(() => !collapsed.value && !props.disabled);
const isUriDataField = computed(() => {
const dataField = props.type == "data";
if (dataField && props.value && "src" in props.value) {
const src = props.value.src;
return src == "url";
}
return false;
});
const previewText = computed(() => attrs.value["text_value"]);
const helpText = computed(() => {
Expand Down Expand Up @@ -285,6 +294,12 @@ function onAlert(value: string | undefined) {
:options="attrs.options"
:optional="attrs.optional"
:multiple="attrs.multiple" />
<FormDataUri
v-else-if="isUriDataField"
:id="id"
v-model="currentValue"
:value="attrs.value"
:multiple="attrs.multiple" />
<FormData
v-else-if="['data', 'data_collection'].includes(props.type)"
:id="id"
Expand Down
5 changes: 5 additions & 0 deletions client/src/components/Landing/ToolLanding.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script setup lang="ts"></script>

<template>
<div>This a place holder for a feature that is not currently implemented.</div>
</template>
61 changes: 61 additions & 0 deletions client/src/components/Landing/WorkflowLanding.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<script setup lang="ts">
import { BAlert } from "bootstrap-vue";
import { onMounted, ref } from "vue";
import { GalaxyApi } from "@/api";
import { type ClaimLandingPayload } from "@/api/landings";
import { errorMessageAsString } from "@/utils/simple-error";
import LoadingSpan from "@/components/LoadingSpan.vue";
import WorkflowRun from "@/components/Workflow/Run/WorkflowRun.vue";
interface Props {
uuid: string;
secret?: string;
}
const props = withDefaults(defineProps<Props>(), {
secret: undefined,
});
const workflowId = ref<string | null>(null);
const errorMessage = ref<string | null>(null);
const requestState = ref<Record<string, never> | null>(null);
onMounted(async () => {
const payload: ClaimLandingPayload = {};
if (props.secret) {
payload["client_secret"] = props.secret;
}
const { data, error } = await GalaxyApi().POST("/api/workflow_landings/{uuid}/claim", {
params: {
path: { uuid: props.uuid },
},
body: payload,
});
if (data) {
workflowId.value = data.workflow_id;
requestState.value = data.request_state;
} else {
errorMessage.value = errorMessageAsString(error);
}
console.log(data);
});
</script>

<template>
<div>
<div v-if="!workflowId">
<LoadingSpan message="Loading workflow parameters" />
</div>
<div v-else-if="errorMessage">
<BAlert variant="danger" show>
{{ errorMessage }}
</BAlert>
</div>
<div v-else>
<WorkflowRun :workflow-id="workflowId" :prefer-simple-form="true" :request-state="requestState" />
</div>
</div>
</template>
3 changes: 3 additions & 0 deletions client/src/components/Workflow/Run/WorkflowRun.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ interface Props {
preferSimpleForm?: boolean;
simpleFormTargetHistory?: string;
simpleFormUseJobCache?: boolean;
requestState?: Record<string, never>;
}
const props = withDefaults(defineProps<Props>(), {
version: undefined,
preferSimpleForm: false,
simpleFormTargetHistory: "current",
simpleFormUseJobCache: false,
requestState: undefined,
});
const loading = ref(true);
Expand Down Expand Up @@ -200,6 +202,7 @@ defineExpose({
:target-history="simpleFormTargetHistory"
:use-job-cache="simpleFormUseJobCache"
:can-mutate-current-history="canRunOnHistory"
:request-state="requestState"
@submissionSuccess="handleInvocations"
@submissionError="handleSubmissionError"
@showAdvanced="showAdvanced" />
Expand Down
11 changes: 10 additions & 1 deletion client/src/components/Workflow/Run/WorkflowRunFormSimple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ export default {
type: Boolean,
required: true,
},
requestState: {
type: Object,
required: false,
},
},
setup() {
const { config, isConfigLoaded } = useConfig(true);
Expand Down Expand Up @@ -135,17 +139,22 @@ export default {
if (isWorkflowInput(step.step_type)) {
const stepName = new String(step.step_index);
const stepLabel = step.step_label || new String(step.step_index + 1);
const stepType = step.step_type;
const help = step.annotation;
const longFormInput = step.inputs[0];
const stepAsInput = Object.assign({}, longFormInput, {
name: stepName,
help: help,
label: stepLabel,
});
if (this.requestState && this.requestState[stepLabel]) {
const value = this.requestState[stepLabel];
stepAsInput.value = value;
}
// disable collection mapping...
stepAsInput.flavor = "module";
inputs.push(stepAsInput);
this.inputTypes[stepName] = step.step_type;
this.inputTypes[stepName] = stepType;
}
});
return inputs;
Expand Down
12 changes: 12 additions & 0 deletions client/src/entry/analysis/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import HistoryImport from "components/HistoryImport";
import InteractiveTools from "components/InteractiveTools/InteractiveTools";
import JobDetails from "components/JobInformation/JobDetails";
import CarbonEmissionsCalculations from "components/JobMetrics/CarbonEmissions/CarbonEmissionsCalculations";
import ToolLanding from "components/Landing/ToolLanding";
import WorkflowLanding from "components/Landing/WorkflowLanding";
import PageDisplay from "components/PageDisplay/PageDisplay";
import PageEditor from "components/PageEditor/PageEditor";
import ToolSuccess from "components/Tool/ToolSuccess";
Expand Down Expand Up @@ -494,6 +496,16 @@ export function getRouter(Galaxy) {
path: "tools/json",
component: ToolsJson,
},
{
path: "tool_landings/:uuid",
component: ToolLanding,
props: true,
},
{
path: "workflow_landings/:uuid",
component: WorkflowLanding,
props: true,
},
{
path: "user",
component: UserPreferences,
Expand Down
5 changes: 5 additions & 0 deletions lib/galaxy/exceptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ class UserActivationRequiredException(MessageException):
err_code = error_codes_by_name["USER_ACTIVATION_REQUIRED"]


class ItemAlreadyClaimedException(MessageException):
status_code = 403
err_code = error_codes_by_name["ITEM_IS_CLAIMED"]


class ObjectNotFound(MessageException):
"""Accessed object was not found"""

Expand Down
5 changes: 5 additions & 0 deletions lib/galaxy/exceptions/error_codes.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@
"code": 403007,
"message": "Action requires account activation."
},
{
"name": "ITEM_IS_CLAIMED",
"code": 403008,
"message": "This item has already been claimed and cannot be re-claimed."
},
{
"name": "USER_REQUIRED",
"code": 403008,
Expand Down
Loading

0 comments on commit dcc301e

Please sign in to comment.