Skip to content

Commit

Permalink
Merge branch 'release_24.1' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
mvdbeek committed Jun 12, 2024
2 parents 4d35888 + 070c6a2 commit 912c1cb
Show file tree
Hide file tree
Showing 12 changed files with 97 additions and 48 deletions.
7 changes: 6 additions & 1 deletion client/src/components/Form/FormElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import { library } from "@fortawesome/fontawesome-svg-core";
import { faCaretSquareDown, faCaretSquareUp } from "@fortawesome/free-regular-svg-icons";
import { faArrowsAltH, faExclamation, faTimes } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { sanitize } from "dompurify";
import type { ComputedRef } from "vue";
import { computed, ref, useAttrs } from "vue";
import { linkify } from "@/utils/utils";
import type { FormParameterAttributes, FormParameterTypes, FormParameterValue } from "./parameterTypes";
import FormBoolean from "./Elements/FormBoolean.vue";
Expand Down Expand Up @@ -181,7 +184,9 @@ const isOptional = computed(() => !isRequired.value && attrs.value["optional"] !
:class="{ alert: hasAlert, 'alert-info': hasAlert }">
<div v-if="hasAlert" class="ui-form-error">
<FontAwesomeIcon class="mr-1" icon="fa-exclamation" />
<span class="ui-form-error-text" v-html="props.error || props.warning" />
<span
class="ui-form-error-text"
v-html="linkify(sanitize(props.error || props.warning, { USE_PROFILES: { html: true } }))" />
</div>

<div class="ui-form-title">
Expand Down
9 changes: 2 additions & 7 deletions client/src/components/Sharing/Embeds/WorkflowEmbed.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { useDebounce } from "@vueuse/core";
import { BButton, BFormCheckbox, BFormInput, BInputGroup, BInputGroupAppend } from "bootstrap-vue";
import { computed, reactive, ref } from "vue";
import { getAppRoot } from "@/onload/loadConfig";
import { copy } from "@/utils/clipboard";
import { getFullAppUrl } from "@/utils/utils";
import ZoomControl from "@/components/Workflow/Editor/ZoomControl.vue";
import WorkflowPublished from "@/components/Workflow/Published/WorkflowPublished.vue";
Expand Down Expand Up @@ -39,13 +39,8 @@ function onChangePosition(event: Event, xy: "x" | "y") {
}
}
const root = computed(() => {
const port = window.location.port ? `:${window.location.port}` : "";
return `${window.location.protocol}//${window.location.hostname}${port}${getAppRoot()}`;
});
const embedUrl = computed(() => {
let url = `${root.value}published/workflow?id=${props.id}&embed=true`;
let url = getFullAppUrl(`published/workflow?id=${props.id}&embed=true`);
url += `&buttons=${settings.buttons}`;
url += `&about=${settings.about}`;
url += `&heading=${settings.heading}`;
Expand Down
8 changes: 2 additions & 6 deletions client/src/components/Sharing/SharingPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getGalaxyInstance } from "@/app";
import { useToast } from "@/composables/toast";
import { getAppRoot } from "@/onload/loadConfig";
import { errorMessageAsString } from "@/utils/simple-error";
import { getFullAppUrl } from "@/utils/utils";
import type { Item, ShareOption } from "./item";
Expand Down Expand Up @@ -52,11 +53,6 @@ const item = ref<Item>({
extra: defaultExtra(),
});
const itemRoot = computed(() => {
const port = window.location.port ? `:${window.location.port}` : "";
return `${window.location.protocol}//${window.location.hostname}${port}${getAppRoot()}`;
});
const itemUrl = reactive({
prefix: "",
slug: "",
Expand All @@ -68,7 +64,7 @@ watch(
if (value) {
const index = value.lastIndexOf("/");
itemUrl.prefix = itemRoot.value + value.substring(0, index + 1);
itemUrl.prefix = getFullAppUrl(value.substring(0, index + 1));
itemUrl.slug = value.substring(index + 1);
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { computed } from "vue";
import { RouterLink } from "vue-router";
import { getAppRoot } from "@/onload/loadConfig";
import { useUserStore } from "@/stores/userStore";
import { getFullAppUrl } from "@/utils/utils";
import Heading from "@/components/Common/Heading.vue";
import CopyToClipboard from "@/components/CopyToClipboard.vue";
Expand Down Expand Up @@ -42,17 +42,12 @@ const gravatarSource = computed(
const publishedByUser = computed(() => `/workflows/list_published?owner=${props.workflowInfo?.owner}`);
const root = computed(() => {
const port = window.location.port ? `:${window.location.port}` : "";
return `${window.location.protocol}//${window.location.hostname}${port}${getAppRoot()}`;
});
const relativeLink = computed(() => {
return `/published/workflow?id=${props.workflowInfo.id}`;
});
const fullLink = computed(() => {
return `${root.value}${relativeLink.value.substring(1)}`;
return getFullAppUrl(relativeLink.value.substring(1));
});
const userOwned = computed(() => {
Expand Down
40 changes: 29 additions & 11 deletions client/src/components/Workflow/WorkflowActionsExtend.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faStar as farStar } from "@fortawesome/free-regular-svg-icons";
import {
faCaretDown,
faCopy,
faDownload,
faFileExport,
faShareAlt,
faStar,
faTrashRestore,
} from "@fortawesome/free-solid-svg-icons";
import { faCopy, faDownload, faLink, faShareAlt, faTrashRestore } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BButton } from "bootstrap-vue";
import { storeToRefs } from "pinia";
Expand All @@ -19,9 +10,11 @@ import { copyWorkflow, undeleteWorkflow } from "@/components/Workflow/workflows.
import { useConfirmDialog } from "@/composables/confirmDialog";
import { Toast } from "@/composables/toast";
import { useUserStore } from "@/stores/userStore";
import { copy } from "@/utils/clipboard";
import { withPrefix } from "@/utils/redirect";
import { getFullAppUrl } from "@/utils/utils";
library.add(faCaretDown, faCopy, faDownload, faFileExport, faShareAlt, farStar, faStar, faTrashRestore);
library.add(faCopy, faDownload, faLink, faShareAlt, faTrashRestore);
interface Props {
workflow: any;
Expand Down Expand Up @@ -72,11 +65,36 @@ async function onRestore() {
Toast.info("Workflow restored");
}
}
const relativeLink = computed(() => {
return `/published/workflow?id=${props.workflow.id}`;
});
const fullLink = computed(() => {
return getFullAppUrl(relativeLink.value.substring(1));
});
function onCopyPublicLink() {
copy(fullLink.value);
Toast.success("Link to workflow copied");
}
</script>

<template>
<div class="workflow-actions-extend flex-gapx-1">
<BButtonGroup>
<BButton
v-if="workflow.published && !workflow.deleted"
id="workflow-copy-public-button"
v-b-tooltip.hover.noninteractive
:size="buttonSize"
title="Copy link to workflow"
variant="outline-primary"
@click="onCopyPublicLink">
<FontAwesomeIcon :icon="faLink" fixed-width />
<span class="compact-view">Link to Workflow</span>
</BButton>

<BButton
v-if="!isAnonymous && !shared && !workflow.deleted"
id="workflow-copy-button"
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/Workflow/WorkflowIndicators.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faGlobe, faLink, faShieldAlt, faUser, faUsers } from "@fortawesome/free-solid-svg-icons";
import { faFileImport, faGlobe, faShieldAlt, faUser, faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BBadge, BButton } from "bootstrap-vue";
import { computed } from "vue";
Expand All @@ -12,7 +12,7 @@ import { copy } from "@/utils/clipboard";
import UtcDate from "@/components/UtcDate.vue";
library.add(faShieldAlt, faLink, faGlobe, faUsers, faUser);
library.add(faFileImport, faGlobe, faShieldAlt, faUsers, faUser);
interface Props {
workflow: any;
Expand Down Expand Up @@ -116,7 +116,7 @@ function onViewUserPublished() {
size="sm"
class="workflow-external-link inline-icon-button"
:title="sourceTitle">
<FontAwesomeIcon :icon="faLink" fixed-width @click="onCopyLink" />
<FontAwesomeIcon :icon="faFileImport" fixed-width @click="onCopyLink" />
</BButton>

<span class="mr-1">
Expand Down
12 changes: 12 additions & 0 deletions client/src/utils/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,16 @@ describe("test utils", () => {
]);
});
});

describe("test getFullAppUrl", () => {
it("should return the full app url", () => {
const appUrl = Utils.getFullAppUrl();
expect(appUrl).toBe("http://localhost/");
});

it("should return the full app url", () => {
const appUrl = Utils.getFullAppUrl("home");
expect(appUrl).toBe("http://localhost/home");
});
});
});
16 changes: 16 additions & 0 deletions client/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,21 @@ export function hasKeys(object: unknown, keys: string[]) {
}
}

/**
* Get the full URL path of the app
*
* @param path Path to append to the URL path
* @returns Full URL path of the app
*/
export function getFullAppUrl(path: string = ""): string {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port ? `:${window.location.port}` : "";
const appRoot = getAppRoot();

return `${protocol}//${hostname}${port}${appRoot}${path}`;
}

export default {
cssLoadFile,
get,
Expand All @@ -460,4 +475,5 @@ export default {
waitForElementToBePresent,
wait,
mergeObjectListsById,
getFullAppUrl,
};
14 changes: 8 additions & 6 deletions lib/galaxy/managers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,13 @@ def create(self, email=None, username=None, password=None, **kwargs):
else:
# Activation is off, every new user is active by default.
user.active = True
self.session().add(user)
session = self.session()
session.add(user)
try:
session = self.session()
with transaction(session):
session.commit()
# TODO:?? flush needed for permissions below? If not, make optional
# Creating a private role will commit the session
self.app.security_agent.create_user_role(user, self.app)
except exc.IntegrityError as db_err:
raise exceptions.Conflict(str(db_err))
self.app.security_agent.create_user_role(user, self.app)
return user

def delete(self, user, flush=True):
Expand Down Expand Up @@ -200,6 +198,10 @@ def purge(self, user, flush=True):
if not user.deleted:
raise exceptions.MessageException(f"User '{user.email}' has not been deleted, so they cannot be purged.")
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
)
# Delete History
for active_history in user.active_histories:
self.session().refresh(active_history)
Expand Down
3 changes: 3 additions & 0 deletions lib/galaxy/model/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,9 @@ def create_private_user_role(self, user):
return self.get_private_user_role(user)

def get_private_user_role(self, user, auto_create=False):
if auto_create and user.id is None:
# New user, directly create private role
return self.create_private_user_role(user)
role = get_private_user_role(user, self.sa_session)
if not role and auto_create:
role = self.create_private_user_role(user)
Expand Down
11 changes: 10 additions & 1 deletion lib/galaxy/tool_util/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,21 @@ def name(cls) -> str:
return cls.__name__

@classmethod
def list_listers(cls) -> List[str]:
def list_linters(cls) -> List[str]:
"""
list the names of all linter derived from Linter
"""
submodules.import_submodules(galaxy.tool_util.linters)
return [s.__name__ for s in cls.__subclasses__()]

list_listers: Callable[[], List[str]] # deprecated alias


# Define the `list_listers` alias outside of the `Linter` class so that
# @classmethod's change to `list_linters`s signature has taken effect and mypy
# doesn't report an [assignment] error
Linter.list_listers = Linter.list_linters


class LintMessage:
"""
Expand Down
10 changes: 4 additions & 6 deletions lib/galaxy/webapps/galaxy/api/workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
status,
)
from gxformat2._yaml import ordered_dump
from markupsafe import escape
from pydantic import (
UUID1,
UUID4,
Expand Down Expand Up @@ -86,7 +85,6 @@
from galaxy.tools import recommendations
from galaxy.tools.parameters import populate_state
from galaxy.tools.parameters.workflow_utils import workflow_building_modes
from galaxy.util.sanitize_html import sanitize_html
from galaxy.web import (
expose_api,
expose_api_raw_anonymous_and_sessionless,
Expand Down Expand Up @@ -268,7 +266,7 @@ def create(self, trans: GalaxyWebTransaction, payload=None, **kwd):
)
import_source = "URL"
except Exception:
raise exceptions.MessageException(f"Failed to open URL '{escape(archive_source)}'.")
raise exceptions.MessageException(f"Failed to open URL '{archive_source}'.")
elif hasattr(archive_file, "file"):
uploaded_file = archive_file.file
uploaded_file_name = uploaded_file.name
Expand Down Expand Up @@ -448,7 +446,7 @@ def update(self, trans: GalaxyWebTransaction, id, payload, **kwds):
name_updated = new_workflow_name and new_workflow_name != stored_workflow.name
steps_updated = "steps" in workflow_dict
if name_updated and not steps_updated:
sanitized_name = sanitize_html(new_workflow_name or old_workflow.name)
sanitized_name = new_workflow_name or old_workflow.name
if not sanitized_name:
raise exceptions.MessageException("Workflow must have a valid name.")
workflow = old_workflow.copy(user=trans.user)
Expand All @@ -472,7 +470,7 @@ def update(self, trans: GalaxyWebTransaction, id, payload, **kwds):
require_flush = True

if "annotation" in workflow_dict and not steps_updated:
newAnnotation = sanitize_html(workflow_dict["annotation"])
newAnnotation = workflow_dict["annotation"]
self.add_item_annotation(trans.sa_session, trans.user, stored_workflow, newAnnotation)
require_flush = True

Expand Down Expand Up @@ -599,7 +597,7 @@ def __api_import_from_archive(self, trans: GalaxyWebTransaction, archive_data, s
workflow = workflow.latest_workflow

response = {
"message": f"Workflow '{escape(workflow.name)}' imported successfully.",
"message": f"Workflow '{workflow.name}' imported successfully.",
"status": "success",
"id": trans.security.encode_id(workflow_id),
}
Expand Down

0 comments on commit 912c1cb

Please sign in to comment.