Skip to content

Commit

Permalink
Merge pull request #18386 from jdavcs/dev_merge_241
Browse files Browse the repository at this point in the history
Merge 24.1
  • Loading branch information
jdavcs authored Jun 13, 2024
2 parents 912c1cb + 1b8fec5 commit 1dcb564
Show file tree
Hide file tree
Showing 23 changed files with 217 additions and 35 deletions.
5 changes: 5 additions & 0 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8394,6 +8394,11 @@ export interface components {
* @default false
*/
use_cached_job?: boolean | null;
/**
* Version
* @description The version of the workflow to invoke.
*/
version?: number | null;
};
/**
* ItemTagsCreatePayload
Expand Down
4 changes: 3 additions & 1 deletion client/src/components/Workflow/Editor/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,9 @@ export default {
this.report.markdown = markdown;
},
onRun() {
const runUrl = `/workflows/run?id=${this.id}`;
const runUrl = `/workflows/run?id=${this.id}${
this.version !== undefined ? `&version=${this.version}` : ""
}`;
this.onNavigate(runUrl);
},
async onNavigate(url, forceSave = false, ignoreChanges = false) {
Expand Down
10 changes: 7 additions & 3 deletions client/src/components/Workflow/Run/WorkflowRun.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ const router = useRouter();
interface Props {
workflowId: string;
version?: string;
preferSimpleForm?: boolean;
simpleFormTargetHistory?: string;
simpleFormUseJobCache?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
version: undefined,
preferSimpleForm: false,
simpleFormTargetHistory: "current",
simpleFormUseJobCache: false,
Expand All @@ -49,7 +51,9 @@ const workflowName = ref("");
const workflowModel: any = ref(null);
const currentHistoryId = computed(() => historyStore.currentHistoryId);
const editorLink = computed(() => `/workflows/edit?id=${props.workflowId}`);
const editorLink = computed(
() => `/workflows/edit?id=${props.workflowId}${props.version ? `&version=${props.version}` : ""}`
);
const historyStatusKey = computed(() => `${currentHistoryId.value}_${lastUpdateTime.value}`);
const isOwner = computed(() => currentUser.value?.username === workflowModel.value.runData.owner);
const lastUpdateTime = computed(() => historyItemsStore.lastUpdateTime);
Expand All @@ -74,7 +78,7 @@ function handleSubmissionError(error: string) {
}
function loadRun() {
getRunData(props.workflowId)
getRunData(props.workflowId, props.version || undefined)
.then((runData) => {
const incomingModel = new WorkflowRunModel(runData);
simpleForm.value = props.preferSimpleForm;
Expand Down Expand Up @@ -116,7 +120,7 @@ function loadRun() {
}
async function onImport() {
const response = await copyWorkflow(props.workflowId, workflowModel.value.runData.owner);
const response = await copyWorkflow(props.workflowId, workflowModel.value.runData.owner, props.version);
router.push(`/workflows/edit?id=${response.id}`);
}
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/Workflow/Run/WorkflowRunForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</span>
</BAlert>
<div class="h4 clearfix mb-3">
<b>Workflow: {{ model.name }}</b>
<b>Workflow: {{ model.name }}</b> <i>(version: {{ model.runData.version + 1 }})</i>
<ButtonSpinner
id="run-workflow"
class="float-right"
Expand Down Expand Up @@ -231,6 +231,7 @@ export default {
// the user is already warned if tool versions are wrong,
// they can still choose to invoke the workflow anyway.
require_exact_tool_versions: false,
version: this.model.runData.version,
};
console.debug("WorkflowRunForm::onExecute()", "Ready for submission.", jobDef);
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/Workflow/Run/WorkflowRunFormSimple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
or send the results to a new one using the run settings ⚙️
</span>
</BAlert>
<b>Workflow: {{ model.name }}</b>
<b>Workflow: {{ model.name }}</b> <i>(version: {{ model.runData.version + 1 }})</i>
<ButtonSpinner
id="run-workflow"
:wait="waitingForRequest"
Expand Down Expand Up @@ -200,6 +200,7 @@ export default {
batch: true,
use_cached_job: this.useCachedJobs,
require_exact_tool_versions: false,
version: this.model.runData.version,
};
if (this.sendToNewHistory) {
data.new_history_name = this.model.name;
Expand Down
8 changes: 6 additions & 2 deletions client/src/components/Workflow/Run/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import { rethrowSimple } from "utils/simple-error";
* for implementation). This contains the data needed to render the UI for workflows.
*
* @param {String} workflowId - (Stored?) Workflow ID to fetch data for.
* @param {String} version - Version of the workflow to fetch.
*/
export async function getRunData(workflowId) {
const url = `${getAppRoot()}api/workflows/${workflowId}/download?style=run`;
export async function getRunData(workflowId, version = null) {
let url = `${getAppRoot()}api/workflows/${workflowId}/download?style=run`;
if (version) {
url += `&version=${version}`;
}
try {
const response = await axios.get(url);
return response.data;
Expand Down
10 changes: 8 additions & 2 deletions client/src/components/Workflow/WorkflowRunButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { library } from "@fortawesome/fontawesome-svg-core";
import { faPlay } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BButton } from "bootstrap-vue";
import { computed } from "vue";
library.add(faPlay);
Expand All @@ -11,9 +12,14 @@ interface Props {
full?: boolean;
title?: string;
disabled?: boolean;
version?: number;
}
defineProps<Props>();
const props = defineProps<Props>();
const runPath = computed(
() => `/workflows/run?id=${props.id}${props.version !== undefined ? `&version=${props.version}` : ""}`
);
</script>

<template>
Expand All @@ -25,7 +31,7 @@ defineProps<Props>();
variant="primary"
size="sm"
:disabled="disabled"
:to="`/workflows/run?id=${id}`">
:to="runPath">
<FontAwesomeIcon :icon="faPlay" fixed-width />

<span v-if="full" v-localize>Run</span>
Expand Down
8 changes: 6 additions & 2 deletions client/src/components/Workflow/workflows.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ export async function updateWorkflow(id: string, changes: object): Promise<Workf
return data;
}

export async function copyWorkflow(id: string, currentOwner: string): Promise<Workflow> {
const { data: workflowData } = await axios.get(withPrefix(`/api/workflows/${id}/download`));
export async function copyWorkflow(id: string, currentOwner: string, version?: string): Promise<Workflow> {
let path = `/api/workflows/${id}/download`;
if (version) {
path += `?version=${version}`;
}
const { data: workflowData } = await axios.get(withPrefix(path));

workflowData.name = `Copy of ${workflowData.name}`;
const currentUsername = useUserStore().currentUser?.username;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ function getWorkflowName() {
size="sm"
variant="secondary"
:disabled="isDeletedWorkflow"
:to="`/workflows/edit?id=${getWorkflowId()}`">
:to="`/workflows/edit?id=${getWorkflowId()}&version=${workflowVersion}`">
<FontAwesomeIcon :icon="faEdit" />
<span v-localize>Edit</span>
</BButton>
Expand All @@ -218,7 +218,8 @@ function getWorkflowName() {
: 'This workflow has been deleted.'
"
:disabled="isDeletedWorkflow"
full />
full
:version="workflowVersion" />
</BButtonGroup>
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions client/src/entry/analysis/modules/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default {
},
workflowParams() {
const workflowId = this.query.workflow_id;
const version = this.query.version;
let preferSimpleForm = this.config.simplified_workflow_run_ui == "prefer";
const preferSimpleFormOverride = this.query.simplified_workflow_run_ui;
if (preferSimpleFormOverride == "prefer") {
Expand All @@ -68,6 +69,7 @@ export default {
const simpleFormUseJobCache = this.config.simplified_workflow_run_ui_job_cache == "on";
return {
workflowId,
version,
preferSimpleForm,
simpleFormTargetHistory,
simpleFormUseJobCache,
Expand Down
5 changes: 5 additions & 0 deletions client/src/entry/analysis/modules/WorkflowEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default {
return {
storedWorkflowId: null,
workflowId: null,
version: null,
editorConfig: null,
editorReloadKey: 0,
};
Expand All @@ -46,6 +47,7 @@ export default {
this.storedWorkflowId = Query.get("id");
this.workflowId = Query.get("workflow_id");
this.version = Query.get("version");
const params = {};
Expand All @@ -54,6 +56,9 @@ export default {
} else if (this.storedWorkflowId) {
params.id = this.storedWorkflowId;
}
if (this.version) {
params.version = this.version;
}
this.editorConfig = await urlData({ url: "/workflow/editor", params });
Expand Down
5 changes: 4 additions & 1 deletion client/src/entry/analysis/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,10 @@ export function getRouter(Galaxy) {
redirect: redirectAnon(),
props: (route) => ({
config: Galaxy.config,
query: { workflow_id: route.query.id },
query: {
workflow_id: route.query.id,
version: route.query.version,
},
}),
},
{
Expand Down
7 changes: 6 additions & 1 deletion lib/galaxy/jobs/runners/pulsar.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,12 @@ def check_watched_item_state(self, job_state):

def _update_job_state_for_status(self, job_state, pulsar_status, full_status=None):
log.debug("(%s) Received status update: %s", job_state.job_id, pulsar_status)
if pulsar_status in ["complete", "cancelled"] or job_state.job_wrapper.get_state() == model.Job.states.STOPPED:
if pulsar_status in ["complete", "cancelled"]:
self.mark_as_finished(job_state)
return None
if job_state.job_wrapper.get_state() == model.Job.states.STOPPED:
client = self.get_client_from_state(job_state)
client.kill()
self.mark_as_finished(job_state)
return None
if pulsar_status in ["failed", "lost"]:
Expand Down
44 changes: 30 additions & 14 deletions lib/galaxy/managers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def purge(self, user, flush=True):
private_role = self.app.security_agent.get_private_user_role(user)
if private_role is None:
raise exceptions.InconsistentDatabase(
"User '%s' private role is missing while attempting to purge deleted user." % user.email
f"User {user.email} private role is missing while attempting to purge deleted user."
)
# Delete History
for active_history in user.active_histories:
Expand Down Expand Up @@ -667,23 +667,11 @@ def get_or_create_remote_user(self, remote_user_email):
self.app.security_agent.user_set_default_permissions(user)
self.app.security_agent.user_set_default_permissions(user, history=True, dataset=True)
elif user is None:
username = remote_user_email.split("@", 1)[0].lower()
random.seed()
user = self.app.model.User(email=remote_user_email)
user.set_random_password(length=12)
user.external = True
# Replace invalid characters in the username
for char in [x for x in username if x not in f"{string.ascii_lowercase + string.digits}-."]:
username = username.replace(char, "-")
# Find a unique username - user can change it later
stmt = select(self.app.model.User).filter_by(username=username).limit(1)
if self.session().scalars(stmt).first():
i = 1
stmt = select(self.app.model.User).filter_by(username=f"{username}-{str(i)}").limit(1)
while self.session().scalars(stmt).first():
i += 1
username += f"-{str(i)}"
user.username = username
user.username = username_from_email(self.session(), remote_user_email, self.app.model.User)
self.session().add(user)
with transaction(self.session()):
self.session().commit()
Expand Down Expand Up @@ -877,3 +865,31 @@ def _add_parsers(self):
)

self.fn_filter_parsers.update({})


def username_from_email(session, email, model_class=User):
"""Get next available username generated based on email"""
username = email.split("@", 1)[0].lower()
username = filter_out_invalid_username_characters(username)
if username_exists(session, username, model_class):
username = generate_next_available_username(session, username, model_class)
return username


def filter_out_invalid_username_characters(username):
"""Replace invalid characters in username"""
for char in [x for x in username if x not in f"{string.ascii_lowercase + string.digits}-."]:
username = username.replace(char, "-")
return username


def username_exists(session, username: str, model_class=User):
return bool(get_user_by_username(session, username, model_class))


def generate_next_available_username(session, username, model_class=User):
"""Generate unique username; user can change it later"""
i = 1
while session.execute(select(model_class).where(model_class.username == f"{username}-{i}")).first():
i += 1
return f"{username}-{i}"
3 changes: 3 additions & 0 deletions lib/galaxy/managers/workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,9 @@ def to_format_2(wf_dict, **kwds):
raise exceptions.RequestParameterInvalidException(f"Unknown workflow style {style}")
if version is not None:
wf_dict["version"] = version
# If returning a run-form workflow for a specific version, use that version's name
if style == "run":
wf_dict["name"] = workflow.name
else:
wf_dict["version"] = len(stored.workflows) - 1
return wf_dict
Expand Down
Loading

0 comments on commit 1dcb564

Please sign in to comment.