diff --git a/.github/workflows/toolshed.yaml b/.github/workflows/toolshed.yaml index 95db16b404ac..48dbb860b391 100644 --- a/.github/workflows/toolshed.yaml +++ b/.github/workflows/toolshed.yaml @@ -61,7 +61,7 @@ jobs: path: 'galaxy root/.venv' key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-toolshed - name: Install dependencies - run: ./scripts/common_startup.sh --skip-client-build + run: ./scripts/common_startup.sh --dev-wheels --skip-client-build working-directory: 'galaxy root' - name: Build Frontend run: | diff --git a/client/gulpfile.js b/client/gulpfile.js index ca806b726c76..9766e256fd16 100644 --- a/client/gulpfile.js +++ b/client/gulpfile.js @@ -120,6 +120,20 @@ function buildPlugins(callback, forceRebuild) { skipBuild = false; } else { if (fs.existsSync(hashFilePath)) { + const hashFileContent = fs.readFileSync(hashFilePath, "utf8").trim(); + const isHash = /^[0-9a-f]{7,40}$/.test(hashFileContent); // Check for a 7 to 40 character hexadecimal string + + if (!isHash) { + console.log(`Hash file for ${pluginName} exists but does not have a valid git hash.`); + skipBuild = false; + } else { + skipBuild = + child_process.spawnSync("git", ["diff", "--quiet", hashFileContent, "--", pluginDir], { + stdio: "inherit", + shell: true, + }).status === 0; + } + skipBuild = child_process.spawnSync("git", ["diff", "--quiet", `$(cat ${hashFilePath})`, "--", pluginDir], { stdio: "inherit", diff --git a/client/src/api/datasets.ts b/client/src/api/datasets.ts index 09948b45ce26..ded071c812af 100644 --- a/client/src/api/datasets.ts +++ b/client/src/api/datasets.ts @@ -1,7 +1,7 @@ import type { FetchArgType } from "openapi-typescript-fetch"; import { DatasetDetails } from "@/api"; -import { fetcher } from "@/api/schema"; +import { components, fetcher } from "@/api/schema"; import { withPrefix } from "@/utils/redirect"; export const datasetsFetcher = fetcher.path("/api/datasets").method("get").create(); @@ -87,3 +87,6 @@ export async function copyDataset( export function getCompositeDatasetLink(historyDatasetId: string, path: string) { return withPrefix(`/api/datasets/${historyDatasetId}/display?filename=${path}`); } + +export type DatasetExtraFiles = components["schemas"]["DatasetExtraFiles"]; +export const fetchDatasetExtraFiles = fetcher.path("/api/datasets/{dataset_id}/extra_files").method("get").create(); diff --git a/client/src/api/groups.ts b/client/src/api/groups.ts index 4f0c3e236e87..e795ccfdbf47 100644 --- a/client/src/api/groups.ts +++ b/client/src/api/groups.ts @@ -1,9 +1,13 @@ import axios from "axios"; -import { components } from "@/api/schema"; +import { components, fetcher } from "@/api/schema"; type GroupModel = components["schemas"]["GroupModel"]; export async function getAllGroups(): Promise { const { data } = await axios.get("/api/groups"); return data; } + +export const deleteGroup = fetcher.path("/api/groups/{group_id}").method("delete").create(); +export const purgeGroup = fetcher.path("/api/groups/{group_id}/purge").method("post").create(); +export const undeleteGroup = fetcher.path("/api/groups/{group_id}/undelete").method("post").create(); diff --git a/client/src/api/jobs.ts b/client/src/api/jobs.ts index b96b65352600..e915a9179098 100644 --- a/client/src/api/jobs.ts +++ b/client/src/api/jobs.ts @@ -1,4 +1,8 @@ -import { fetcher } from "@/api/schema"; +import { components, fetcher } from "@/api/schema"; + +export type JobDestinationParams = components["schemas"]["JobDestinationParams"]; export const jobLockStatus = fetcher.path("/api/job_lock").method("get").create(); export const jobLockUpdate = fetcher.path("/api/job_lock").method("put").create(); + +export const fetchJobDestinationParams = fetcher.path("/api/jobs/{job_id}/destination_params").method("get").create(); diff --git a/client/src/api/roles.ts b/client/src/api/roles.ts index 61fde1360a92..4667ca53a2b5 100644 --- a/client/src/api/roles.ts +++ b/client/src/api/roles.ts @@ -5,3 +5,7 @@ export async function getAllRoles() { const { data } = await getRoles({}); return data; } + +export const deleteRole = fetcher.path("/api/roles/{id}").method("delete").create(); +export const purgeRole = fetcher.path("/api/roles/{id}/purge").method("post").create(); +export const undeleteRole = fetcher.path("/api/roles/{id}/undelete").method("post").create(); diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 7e4f142a7a05..bd06205258ec 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -155,6 +155,10 @@ export interface paths { */ get: operations["converted_ext_api_datasets__dataset_id__converted__ext__get"]; }; + "/api/datasets/{dataset_id}/extra_files": { + /** Get the list of extra files/directories associated with a dataset. */ + get: operations["extra_files_api_datasets__dataset_id__extra_files_get"]; + }; "/api/datasets/{dataset_id}/get_content_as_text": { /** Returns dataset content as Text. */ get: operations["get_content_as_text_api_datasets__dataset_id__get_content_as_text_get"]; @@ -392,6 +396,12 @@ export interface paths { get: operations["show_group_api_groups__group_id__get"]; /** Modifies a group. */ put: operations["update_api_groups__group_id__put"]; + /** Delete */ + delete: operations["delete_api_groups__group_id__delete"]; + }; + "/api/groups/{group_id}/purge": { + /** Purge */ + post: operations["purge_api_groups__group_id__purge_post"]; }; "/api/groups/{group_id}/roles": { /** Displays a collection (list) of groups. */ @@ -405,6 +415,10 @@ export interface paths { /** Removes a role from a group */ delete: operations["delete_api_groups__group_id__roles__role_id__delete"]; }; + "/api/groups/{group_id}/undelete": { + /** Undelete */ + post: operations["undelete_api_groups__group_id__undelete_post"]; + }; "/api/groups/{group_id}/user/{user_id}": { /** * Displays information about a group user. @@ -640,8 +654,8 @@ export interface paths { head: operations["history_contents_display_api_histories__history_id__contents__history_content_id__display_head"]; }; "/api/histories/{history_id}/contents/{history_content_id}/extra_files": { - /** Generate list of extra files. */ - get: operations["extra_files_api_histories__history_id__contents__history_content_id__extra_files_get"]; + /** Get the list of extra files/directories associated with a dataset. */ + get: operations["extra_files_history_api_histories__history_id__contents__history_content_id__extra_files_get"]; }; "/api/histories/{history_id}/contents/{history_content_id}/metadata_file": { /** Returns the metadata file associated with this history item. */ @@ -1361,6 +1375,16 @@ export interface paths { "/api/roles/{id}": { /** Show */ get: operations["show_api_roles__id__get"]; + /** Delete */ + delete: operations["delete_api_roles__id__delete"]; + }; + "/api/roles/{id}/purge": { + /** Purge */ + post: operations["purge_api_roles__id__purge_post"]; + }; + "/api/roles/{id}/undelete": { + /** Undelete */ + post: operations["undelete_api_roles__id__undelete_post"]; }; "/api/short_term_storage/{storage_request_id}": { /** Serve the staged download specified by request ID. */ @@ -1592,6 +1616,14 @@ export interface paths { /** Remove the object from user's favorites */ delete: operations["remove_favorite_api_users__user_id__favorites__object_type___object_id__delete"]; }; + "/api/users/{user_id}/recalculate_disk_usage": { + /** Triggers a recalculation of the current user disk usage. */ + put: operations["recalculate_disk_usage_by_user_id_api_users__user_id__recalculate_disk_usage_put"]; + }; + "/api/users/{user_id}/send_activation_email": { + /** Sends activation email to user. */ + post: operations["send_activation_email_api_users__user_id__send_activation_email_post"]; + }; "/api/users/{user_id}/theme/{theme}": { /** Set the user's theme choice */ put: operations["set_theme_api_users__user_id__theme__theme__put"]; @@ -2590,6 +2622,8 @@ export interface components { * @default false */ deferred?: boolean; + /** Description */ + description?: string; elements_from?: components["schemas"]["ElementsFromType"]; /** * Ext @@ -3472,6 +3506,11 @@ export interface components { */ error_message: string; }; + /** + * DatasetExtraFiles + * @description A list of extra files associated with a dataset. + */ + DatasetExtraFiles: components["schemas"]["ExtraFileEntry"][]; /** * DatasetInheritanceChain * @default [] @@ -4340,6 +4379,16 @@ export interface components { }; /** ExportTaskListResponse */ ExportTaskListResponse: components["schemas"]["ObjectExportTaskResponse"][]; + /** ExtraFileEntry */ + ExtraFileEntry: { + /** @description The class of this entry, either File or Directory. */ + class: components["schemas"]["ExtraFilesEntryClass"]; + /** + * Path + * @description Relative path to the file or directory. + */ + path: string; + }; /** ExtraFiles */ ExtraFiles: { /** @@ -4352,6 +4401,12 @@ export interface components { items_from?: string; src: components["schemas"]["Src"]; }; + /** + * ExtraFilesEntryClass + * @description An enumeration. + * @enum {string} + */ + ExtraFilesEntryClass: "Directory" | "File"; /** FavoriteObject */ FavoriteObject: { /** @@ -4415,6 +4470,8 @@ export interface components { * @default false */ deferred?: boolean; + /** Description */ + description?: string; elements_from?: components["schemas"]["ElementsFromType"]; /** * Ext @@ -4633,6 +4690,8 @@ export interface components { * @default false */ deferred?: boolean; + /** Description */ + description?: string; elements_from?: components["schemas"]["ElementsFromType"]; /** * Ext @@ -6386,7 +6445,7 @@ export interface components { */ ItemTagsCreatePayload: { /** value of the item tag */ - value: string; + value?: string; }; /** * ItemTagsListResponse @@ -7540,6 +7599,8 @@ export interface components { * @default false */ deferred?: boolean; + /** Description */ + description?: string; /** Elements */ elements: ( | ( @@ -8135,6 +8196,8 @@ export interface components { * @default false */ deferred?: boolean; + /** Description */ + description?: string; elements_from?: components["schemas"]["ElementsFromType"]; /** * Ext @@ -8193,6 +8256,8 @@ export interface components { * @default false */ deferred?: boolean; + /** Description */ + description?: string; elements_from?: components["schemas"]["ElementsFromType"]; /** * Ext @@ -8646,6 +8711,8 @@ export interface components { * @default false */ deferred?: boolean; + /** Description */ + description?: string; elements_from?: components["schemas"]["ElementsFromType"]; /** * Ext @@ -9496,6 +9563,8 @@ export interface components { * @default false */ deferred?: boolean; + /** Description */ + description?: string; elements_from?: components["schemas"]["ElementsFromType"]; /** * Ext @@ -10775,6 +10844,33 @@ export interface operations { }; }; }; + extra_files_api_datasets__dataset_id__extra_files_get: { + /** Get the list of extra files/directories associated with a dataset. */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string; + }; + /** @description The encoded database identifier of the dataset. */ + path: { + dataset_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["DatasetExtraFiles"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; get_content_as_text_api_datasets__dataset_id__get_content_as_text_get: { /** Returns dataset content as Text. */ parameters: { @@ -12010,6 +12106,58 @@ export interface operations { }; }; }; + delete_api_groups__group_id__delete: { + /** Delete */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string; + }; + path: { + group_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": Record; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + purge_api_groups__group_id__purge_post: { + /** Purge */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string; + }; + path: { + group_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": Record; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; group_roles_api_groups__group_id__roles_get: { /** Displays a collection (list) of groups. */ parameters: { @@ -12124,6 +12272,32 @@ export interface operations { }; }; }; + undelete_api_groups__group_id__undelete_post: { + /** Undelete */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string; + }; + path: { + group_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": Record; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; group_user_api_groups__group_id__user__user_id__get: { /** * Displays information about a group user. @@ -13621,8 +13795,8 @@ export interface operations { }; }; }; - extra_files_api_histories__history_id__contents__history_content_id__extra_files_get: { - /** Generate list of extra files. */ + extra_files_history_api_histories__history_id__contents__history_content_id__extra_files_get: { + /** Get the list of extra files/directories associated with a dataset. */ parameters: { /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ header?: { @@ -13639,7 +13813,7 @@ export interface operations { /** @description Successful Response */ 200: { content: { - "application/json": Record; + "application/json": components["schemas"]["DatasetExtraFiles"]; }; }; /** @description Validation Error */ @@ -13780,7 +13954,7 @@ export interface operations { history_id: string; }; }; - requestBody: { + requestBody?: { content: { "application/json": components["schemas"]["ItemTagsCreatePayload"]; }; @@ -15087,7 +15261,7 @@ export interface operations { tag_name: string; }; }; - requestBody: { + requestBody?: { content: { "application/json": components["schemas"]["ItemTagsCreatePayload"]; }; @@ -17598,6 +17772,84 @@ export interface operations { }; }; }; + delete_api_roles__id__delete: { + /** Delete */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string; + }; + path: { + id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["RoleModelResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + purge_api_roles__id__purge_post: { + /** Purge */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string; + }; + path: { + id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["RoleModelResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + undelete_api_roles__id__undelete_post: { + /** Undelete */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string; + }; + path: { + id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["RoleModelResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; serve_api_short_term_storage__storage_request_id__get: { /** Serve the staged download specified by request ID. */ parameters: { @@ -18972,6 +19224,62 @@ export interface operations { }; }; }; + recalculate_disk_usage_by_user_id_api_users__user_id__recalculate_disk_usage_put: { + /** Triggers a recalculation of the current user disk usage. */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string; + }; + /** @description The ID of the user to get. */ + path: { + user_id: string; + }; + }; + responses: { + /** @description The asynchronous task summary to track the task state. */ + 200: { + content: { + "application/json": components["schemas"]["AsyncTaskResultSummary"]; + }; + }; + /** @description The background task was submitted but there is no status tracking ID available. */ + 204: never; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + send_activation_email_api_users__user_id__send_activation_email_post: { + /** Sends activation email to user. */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string; + }; + /** @description The ID of the user to get. */ + path: { + user_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": Record; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; set_theme_api_users__user_id__theme__theme__put: { /** Set the user's theme choice */ parameters: { @@ -19846,7 +20154,7 @@ export interface operations { tag_name: string; }; }; - requestBody: { + requestBody?: { content: { "application/json": components["schemas"]["ItemTagsCreatePayload"]; }; diff --git a/client/src/api/users.ts b/client/src/api/users.ts index d3a2a7ac010f..7a59d96ea839 100644 --- a/client/src/api/users.ts +++ b/client/src/api/users.ts @@ -1,7 +1,15 @@ import { fetcher } from "@/api/schema"; -export const recalculateDiskUsage = fetcher.path("/api/users/current/recalculate_disk_usage").method("put").create(); +export const createApiKey = fetcher.path("/api/users/{user_id}/api_key").method("post").create(); +export const deleteUser = fetcher.path("/api/users/{user_id}").method("delete").create(); export const fetchQuotaUsages = fetcher.path("/api/users/{user_id}/usage").method("get").create(); +export const recalculateDiskUsage = fetcher.path("/api/users/current/recalculate_disk_usage").method("put").create(); +export const recalculateDiskUsageByUserId = fetcher + .path("/api/users/{user_id}/recalculate_disk_usage") + .method("put") + .create(); +export const sendActivationEmail = fetcher.path("/api/users/{user_id}/send_activation_email").method("post").create(); +export const undeleteUser = fetcher.path("/api/users/deleted/{user_id}/undelete").method("post").create(); const getUsers = fetcher.path("/api/users").method("get").create(); export async function getAllUsers() { diff --git a/client/src/components/Dataset/compositeDatasetUtils.js b/client/src/components/Dataset/compositeDatasetUtils.js deleted file mode 100644 index 087f61ad9696..000000000000 --- a/client/src/components/Dataset/compositeDatasetUtils.js +++ /dev/null @@ -1,36 +0,0 @@ -import { getCompositeDatasetLink } from "@/api/datasets"; - -import store from "../../store/index"; - -export const getPathDestination = async (history_dataset_id, path) => { - const computePathDestination = (pathDestination) => { - if (path === undefined || path === "undefined") { - return pathDestination; - } - - const filepath = path; - - const datasetEntry = datasetContent.find((datasetEntry) => { - return filepath === datasetEntry.path; - }); - - if (datasetEntry) { - if (datasetEntry.class === "Directory") { - pathDestination.isDirectory = true; - pathDestination.filepath = filepath; - return pathDestination; - } - pathDestination.fileLink = getCompositeDatasetLink(history_dataset_id, datasetEntry.path); - } - return pathDestination; - }; - - let datasetContent = store.getters.getDatasetExtFiles(history_dataset_id); - - if (datasetContent == null) { - await store.dispatch("fetchDatasetExtFiles", history_dataset_id); - datasetContent = store.getters.getDatasetExtFiles(history_dataset_id); - } - - return computePathDestination({ datasetContent: datasetContent }); -}; diff --git a/client/src/components/Dataset/compositeDatasetUtils.ts b/client/src/components/Dataset/compositeDatasetUtils.ts new file mode 100644 index 000000000000..763bb846306a --- /dev/null +++ b/client/src/components/Dataset/compositeDatasetUtils.ts @@ -0,0 +1,45 @@ +import { DatasetExtraFiles, getCompositeDatasetLink } from "@/api/datasets"; +import { useDatasetExtraFilesStore } from "@/stores/datasetExtraFilesStore"; + +interface PathDestination { + datasetContent: DatasetExtraFiles; + isDirectory: boolean; + filepath?: string; + fileLink?: string; +} + +export async function getPathDestination(dataset_id: string, path?: string): Promise { + const datasetExtraFilesStore = useDatasetExtraFilesStore(); + + let datasetExtraFiles = datasetExtraFilesStore.getDatasetExtraFiles(dataset_id); + if (!datasetExtraFiles) { + await datasetExtraFilesStore.fetchDatasetExtFilesByDatasetId({ id: dataset_id }); + datasetExtraFiles = datasetExtraFilesStore.getDatasetExtraFiles(dataset_id); + } + + if (datasetExtraFiles === null) { + return null; + } + + const pathDestination: PathDestination = { datasetContent: datasetExtraFiles, isDirectory: false, filepath: path }; + + if (path === undefined || path === "undefined") { + return pathDestination; + } + + const filepath = path; + + const datasetEntry = datasetExtraFiles?.find((entry) => { + return filepath === entry.path; + }); + + if (datasetEntry) { + if (datasetEntry.class === "Directory") { + pathDestination.isDirectory = true; + pathDestination.filepath = filepath; + return pathDestination; + } + pathDestination.fileLink = getCompositeDatasetLink(dataset_id, datasetEntry.path); + } + return pathDestination; +} diff --git a/client/src/components/Grid/GridElements/GridBoolean.vue b/client/src/components/Grid/GridElements/GridBoolean.vue new file mode 100644 index 000000000000..c98c590290a3 --- /dev/null +++ b/client/src/components/Grid/GridElements/GridBoolean.vue @@ -0,0 +1,14 @@ + + + diff --git a/client/src/components/Grid/GridElements/GridOperations.vue b/client/src/components/Grid/GridElements/GridOperations.vue index d135109286fc..8f1fae493d6b 100644 --- a/client/src/components/Grid/GridElements/GridOperations.vue +++ b/client/src/components/Grid/GridElements/GridOperations.vue @@ -4,12 +4,17 @@ import { faCaretDown } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import type { Operation, RowData } from "@/components/Grid/configs/types"; +import { useConfig } from "@/composables/config"; +import type { GalaxyConfiguration } from "@/stores/configurationStore"; library.add(faCaretDown); +const { config, isConfigLoaded } = useConfig(); + interface Props { rowData: RowData; operations: Array; + title: string; } const props = defineProps(); @@ -21,13 +26,13 @@ const emit = defineEmits<{ /** * Availibility of operations might required certain conditions */ -function hasCondition(conditionHandler: (rowData: RowData) => Boolean) { - return conditionHandler ? conditionHandler(props.rowData) : true; +function hasCondition(conditionHandler: (rowData: RowData, config: GalaxyConfiguration) => Boolean) { + return conditionHandler ? conditionHandler(props.rowData, config) : true; }