diff --git a/src/core/clients/opencga/api/Admin.js b/src/core/clients/opencga/api/Admin.js index 3f6204d41..b096169fe 100644 --- a/src/core/clients/opencga/api/Admin.js +++ b/src/core/clients/opencga/api/Admin.js @@ -37,7 +37,7 @@ export default class Admin extends OpenCGAParentClass { /** Group by operation * @param {String} fields - Comma separated list of fields by which to group by. * @param {"AUDIT NOTE ORGANIZATION USER PROJECT STUDY FILE SAMPLE JOB INDIVIDUAL COHORT DISEASE_PANEL FAMILY CLINICAL_ANALYSIS - * INTERPRETATION VARIANT ALIGNMENT CLINICAL EXPRESSION RGA FUNCTIONAL"} entity - Entity to be grouped by. + * INTERPRETATION VARIANT ALIGNMENT CLINICAL EXPRESSION RGA WORKFLOW FUNCTIONAL"} entity - Entity to be grouped by. * @param {Object} [params] - The Object containing the following optional parameters: * @param {Boolean} [params.count] - Count the number of elements matching the group. * @param {Number} [params.limit = "50"] - Maximum number of documents (groups) to be returned. The default value is 50. diff --git a/src/core/clients/opencga/api/Job.js b/src/core/clients/opencga/api/Job.js index ffc0b066f..913b7b55d 100644 --- a/src/core/clients/opencga/api/Job.js +++ b/src/core/clients/opencga/api/Job.js @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. * WARNING: AUTOGENERATED CODE - * + * * This code was generated by a tool. - * + * * Manual changes to this file may cause unexpected behavior in your application. - * Manual changes to this file will be overwritten if the code is regenerated. + * Manual changes to this file will be overwritten if the code is regenerated. * **/ @@ -66,6 +66,12 @@ export default class Job extends OpenCGAParentClass { * @param {String} [params.toolId] - Tool ID executed by the job. Also admits basic regular expressions using the operator '~', i.e. * '~{perl-regex}' e.g. '~value' for case sensitive, '~/value/i' for case insensitive search. * @param {String} [params.toolType] - Tool type executed by the job [OPERATION, ANALYSIS]. + * @param {String} [params.tool.externalExecutor.id] - Id of the external executor. This field is only applicable for jobs executed by an + * external executor. + * @param {String} [params.parentId] - Job id that generated this job (if any). + * @param {Boolean} [params.dryRun] - Flag indicating that the job will be executed in dry-run mode. In this mode, OpenCGA will validate + * that all parameters and prerequisites are correctly set for successful execution, but the job will not actually run. + * @param {Boolean} [params.internal.killJobRequested] - Flag indicating that the user requested to kill the job. * @param {String} [params.userId] - User that created the job. * @param {String} [params.priority] - Priority of the job. * @param {String} [params.status] - Filter by status. @@ -120,6 +126,12 @@ export default class Job extends OpenCGAParentClass { * @param {String} [params.toolId] - Tool ID executed by the job. Also admits basic regular expressions using the operator '~', i.e. * '~{perl-regex}' e.g. '~value' for case sensitive, '~/value/i' for case insensitive search. * @param {String} [params.toolType] - Tool type executed by the job [OPERATION, ANALYSIS]. + * @param {String} [params.tool.externalExecutor.id] - Id of the external executor. This field is only applicable for jobs executed by an + * external executor. + * @param {String} [params.parentId] - Job id that generated this job (if any). + * @param {Boolean} [params.dryRun] - Flag indicating that the job will be executed in dry-run mode. In this mode, OpenCGA will validate + * that all parameters and prerequisites are correctly set for successful execution, but the job will not actually run. + * @param {Boolean} [params.internal.killJobRequested] - Flag indicating that the user requested to kill the job. * @param {String} [params.userId] - User that created the job. * @param {String} [params.priority] - Priority of the job. * @param {String} [params.status] - Filter by status. @@ -141,6 +153,44 @@ export default class Job extends OpenCGAParentClass { return this._get("jobs", null, null, null, "search", params); } + /** Execute an analysis from a custom binary. + * @param {Object} data - body. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @param {String} [params.jobId] - Job ID. It must be a unique string within the study. An ID will be autogenerated automatically if not + * provided. + * @param {String} [params.jobDescription] - Job description. + * @param {String} [params.jobDependsOn] - Comma separated list of existing job IDs the job will depend on. + * @param {String} [params.jobTags] - Job tags. + * @param {String} [params.jobScheduledStartTime] - Time when the job is scheduled to start. + * @param {String} [params.jobPriority] - Priority of the job. + * @param {Boolean} [params.jobDryRun] - Flag indicating that the job will be executed in dry-run mode. In this mode, OpenCGA will + * validate that all parameters and prerequisites are correctly set for successful execution, but the job will not actually run. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + buildTool(data, params) { + return this._post("jobs", null, "tool", null, "build", data, params); + } + + /** Execute an analysis from a custom binary. + * @param {Object} data - NextFlow run parameters. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @param {String} [params.jobId] - Job ID. It must be a unique string within the study. An ID will be autogenerated automatically if not + * provided. + * @param {String} [params.jobDescription] - Job description. + * @param {String} [params.jobDependsOn] - Comma separated list of existing job IDs the job will depend on. + * @param {String} [params.jobTags] - Job tags. + * @param {String} [params.jobScheduledStartTime] - Time when the job is scheduled to start. + * @param {String} [params.jobPriority] - Priority of the job. + * @param {Boolean} [params.jobDryRun] - Flag indicating that the job will be executed in dry-run mode. In this mode, OpenCGA will + * validate that all parameters and prerequisites are correctly set for successful execution, but the job will not actually run. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + runTool(data, params) { + return this._post("jobs", null, "tool", null, "run", data, params); + } + /** Provide a summary of the running jobs * @param {Object} [params] - The Object containing the following optional parameters: * @param {Number} [params.limit = "20"] - Maximum number of jobs to be returned. The default value is 20. @@ -241,4 +291,4 @@ export default class Job extends OpenCGAParentClass { return this._get("jobs", job, "log", null, "tail", params); } -} +} \ No newline at end of file diff --git a/src/core/clients/opencga/api/Organization.js b/src/core/clients/opencga/api/Organization.js index 37cbe70cb..09b794292 100644 --- a/src/core/clients/opencga/api/Organization.js +++ b/src/core/clients/opencga/api/Organization.js @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. * WARNING: AUTOGENERATED CODE - * + * * This code was generated by a tool. - * + * * Manual changes to this file may cause unexpected behavior in your application. - * Manual changes to this file will be overwritten if the code is regenerated. + * Manual changes to this file will be overwritten if the code is regenerated. * **/ @@ -180,4 +180,4 @@ export default class Organization extends OpenCGAParentClass { return this._post("organizations", organization, null, null, "update", data, params); } -} +} \ No newline at end of file diff --git a/src/core/clients/opencga/api/Study.js b/src/core/clients/opencga/api/Study.js index c638ea47b..0c36cba70 100644 --- a/src/core/clients/opencga/api/Study.js +++ b/src/core/clients/opencga/api/Study.js @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. * WARNING: AUTOGENERATED CODE - * + * * This code was generated by a tool. - * + * * Manual changes to this file may cause unexpected behavior in your application. - * Manual changes to this file will be overwritten if the code is regenerated. + * Manual changes to this file will be overwritten if the code is regenerated. * **/ @@ -119,7 +119,7 @@ export default class Study extends OpenCGAParentClass { * @param {String} [params.userId] - User ID. * @param {String} [params.action] - Action performed by the user. * @param {"AUDIT NOTE ORGANIZATION USER PROJECT STUDY FILE SAMPLE JOB INDIVIDUAL COHORT DISEASE_PANEL FAMILY CLINICAL_ANALYSIS - * INTERPRETATION VARIANT ALIGNMENT CLINICAL EXPRESSION RGA FUNCTIONAL"} [params.resource] - Resource involved. + * INTERPRETATION VARIANT ALIGNMENT CLINICAL EXPRESSION RGA WORKFLOW FUNCTIONAL"} [params.resource] - Resource involved. * @param {String} [params.resourceId] - Resource ID. * @param {String} [params.resourceUuid] - resource UUID. * @param {"SUCCESS ERROR"} [params.status] - Filter by status. @@ -342,4 +342,4 @@ export default class Study extends OpenCGAParentClass { return this._post("studies", study, "variableSets", variableSet, "variables/update", data, params); } -} +} \ No newline at end of file diff --git a/src/core/clients/opencga/api/Variant.js b/src/core/clients/opencga/api/Variant.js index 7d8b01966..4071bb5b2 100644 --- a/src/core/clients/opencga/api/Variant.js +++ b/src/core/clients/opencga/api/Variant.js @@ -444,6 +444,25 @@ export default class Variant extends OpenCGAParentClass { return this._post("analysis", null, "variant/knockout", null, "run", data, params); } + /** BCFtools liftover plugin maps coordinates from assembly 37 to 38. + * @param {Object} data - BCFtools +liftover plugin params. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.study] - study. + * @param {String} [params.jobId] - Job ID. It must be a unique string within the study. An ID will be autogenerated automatically if not + * provided. + * @param {String} [params.jobDescription] - Job description. + * @param {String} [params.jobDependsOn] - Comma separated list of existing job IDs the job will depend on. + * @param {String} [params.jobTags] - Job tags. + * @param {String} [params.jobScheduledStartTime] - Time when the job is scheduled to start. + * @param {String} [params.jobPriority] - Priority of the job. + * @param {Boolean} [params.jobDryRun] - Flag indicating that the job will be executed in dry-run mode. In this mode, OpenCGA will + * validate that all parameters and prerequisites are correctly set for successful execution, but the job will not actually run. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + runLiftover(data, params) { + return this._post("analysis", null, "variant/liftover", null, "run", data, params); + } + /** Run mendelian error analysis to infer uniparental disomy regions. * @param {Object} data - Mendelian error analysis params. * @param {Object} [params] - The Object containing the following optional parameters: diff --git a/src/core/clients/opencga/api/VariantOperation.js b/src/core/clients/opencga/api/VariantOperation.js index 0d79024c4..feb3959ec 100644 --- a/src/core/clients/opencga/api/VariantOperation.js +++ b/src/core/clients/opencga/api/VariantOperation.js @@ -488,6 +488,16 @@ export default class VariantOperation extends OpenCGAParentClass { return this._delete("operation", null, "variant/secondaryIndex", null, "delete", params); } + /** Execute Variant Setup to allow using the variant engine. This setup is necessary before starting any variant operation. + * @param {Object} [data] - Variant setup params. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + setupVariant(data, params) { + return this._post("operation", null, "variant", null, "setup", data, params); + } + /** Deletes the VariantStats of a cohort/s from the database * @param {Object} data - Variant stats delete params. * @param {Object} [params] - The Object containing the following optional parameters: diff --git a/src/core/clients/opencga/api/Workflow.js b/src/core/clients/opencga/api/Workflow.js new file mode 100644 index 000000000..7d6edc607 --- /dev/null +++ b/src/core/clients/opencga/api/Workflow.js @@ -0,0 +1,206 @@ +/** + * Copyright 2015-2024 OpenCB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * WARNING: AUTOGENERATED CODE + * + * This code was generated by a tool. + * + * Manual changes to this file may cause unexpected behavior in your application. + * Manual changes to this file will be overwritten if the code is regenerated. + * +**/ + +import OpenCGAParentClass from "./../opencga-parent-class.js"; + + +/** + * This class contains the methods for the "Workflow" resource + */ + +export default class Workflow extends OpenCGAParentClass { + + constructor(config) { + super(config); + } + + /** Update the set of permissions granted for the member + * @param {String} members - Comma separated list of user or group ids. + * @param {Object} data - JSON containing the parameters to update the permissions. + * @param {"SET ADD REMOVE RESET"} action = "ADD" - Action to be performed [ADD, SET, REMOVE or RESET]. The default value is ADD. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + updateAcl(members, action, data, params) { + return this._post("workflows", null, "acl", members, "update", data, {action, ...params}); + } + + /** Create a workflow + * @param {Object} data - JSON containing workflow information. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.include] - Fields included in the response, whole JSON path must be provided. + * @param {String} [params.exclude] - Fields excluded in the response, whole JSON path must be provided. + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @param {Boolean} [params.includeResult = "false"] - Flag indicating to include the created or updated document result in the response. + * The default value is false. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + create(data, params) { + return this._post("workflows", null, null, null, "create", data, params); + } + + /** Workflow distinct method + * @param {String} field - Comma separated list of fields for which to obtain the distinct values. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @param {String} [params.id] - Comma separated list of workflow IDs up to a maximum of 100. Also admits basic regular expressions using + * the operator '~', i.e. '~{perl-regex}' e.g. '~value' for case sensitive, '~/value/i' for case insensitive search. + * @param {String} [params.name] - Comma separated list of workflow names up to a maximum of 100. Also admits basic regular expressions + * using the operator '~', i.e. '~{perl-regex}' e.g. '~value' for case sensitive, '~/value/i' for case insensitive search. + * @param {String} [params.uuid] - Comma separated list of workflow UUIDs up to a maximum of 100. + * @param {String} [params.tags] - Comma separated list of tags. + * @param {Boolean} [params.draft] - Boolean field indicating whether the workflow is a draft or not. + * @param {String} [params.internal.registrationUserId] - UserId that created the workflow. + * @param {String} [params.manager.id] - Id of the workflow system (Allowed values: NEXTFLOW). + * @param {String} [params.type] - Workflow type. Allowed types: [CLINICAL_INTERPRETATION, SECONDARY_ANALYSIS, RESEARCH or OTHER]. + * @param {String} [params.creationDate] - Creation date. Format: yyyyMMddHHmmss. Examples: >2018, 2017-2018, <201805. + * @param {String} [params.modificationDate] - Modification date. Format: yyyyMMddHHmmss. Examples: >2018, 2017-2018, <201805. + * @param {String} [params.acl] - Filter entries for which a user has the provided permissions. Format: acl={user}:{permissions}. + * Example: acl=john:WRITE,WRITE_ANNOTATIONS will return all entries for which user john has both WRITE and WRITE_ANNOTATIONS + * permissions. Only study owners or administrators can query by this field. . + * @param {String} [params.release] - Release when it was created. + * @param {Number} [params.snapshot] - Snapshot value (Latest version of the entry in the specified release). + * @param {Boolean} [params.deleted = "false"] - Boolean to retrieve deleted entries. The default value is false. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + distinct(field, params) { + return this._get("workflows", null, null, null, "distinct", {field, ...params}); + } + + /** Execute a Nextflow analysis. + * @param {Object} data - Repository parameters. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + importWorkflow(data, params) { + return this._post("workflows", null, null, null, "import", data, params); + } + + /** Execute a Nextflow analysis. + * @param {Object} data - NextFlow run parameters. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @param {String} [params.jobId] - Job ID. It must be a unique string within the study. An ID will be autogenerated automatically if not + * provided. + * @param {String} [params.jobDescription] - Job description. + * @param {String} [params.jobDependsOn] - Comma separated list of existing job IDs the job will depend on. + * @param {String} [params.jobTags] - Job tags. + * @param {String} [params.jobScheduledStartTime] - Time when the job is scheduled to start. + * @param {String} [params.jobPriority] - Priority of the job. + * @param {Boolean} [params.jobDryRun] - Flag indicating that the job will be executed in dry-run mode. In this mode, OpenCGA will + * validate that all parameters and prerequisites are correctly set for successful execution, but the job will not actually run. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + run(data, params) { + return this._post("workflows", null, null, null, "run", data, params); + } + + /** Workflow search method + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.include] - Fields included in the response, whole JSON path must be provided. + * @param {String} [params.exclude] - Fields excluded in the response, whole JSON path must be provided. + * @param {Number} [params.limit] - Number of results to be returned. + * @param {Number} [params.skip] - Number of results to skip. + * @param {Boolean} [params.count = "false"] - Get the total number of results matching the query. Deactivated by default. The default + * value is false. + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @param {String} [params.id] - Comma separated list of workflow IDs up to a maximum of 100. Also admits basic regular expressions using + * the operator '~', i.e. '~{perl-regex}' e.g. '~value' for case sensitive, '~/value/i' for case insensitive search. + * @param {String} [params.name] - Comma separated list of workflow names up to a maximum of 100. Also admits basic regular expressions + * using the operator '~', i.e. '~{perl-regex}' e.g. '~value' for case sensitive, '~/value/i' for case insensitive search. + * @param {String} [params.uuid] - Comma separated list of workflow UUIDs up to a maximum of 100. + * @param {String} [params.tags] - Comma separated list of tags. + * @param {Boolean} [params.draft] - Boolean field indicating whether the workflow is a draft or not. + * @param {String} [params.internal.registrationUserId] - UserId that created the workflow. + * @param {String} [params.manager.id] - Id of the workflow system (Allowed values: NEXTFLOW). + * @param {String} [params.type] - Workflow type. Allowed types: [CLINICAL_INTERPRETATION, SECONDARY_ANALYSIS, RESEARCH or OTHER]. + * @param {String} [params.creationDate] - Creation date. Format: yyyyMMddHHmmss. Examples: >2018, 2017-2018, <201805. + * @param {String} [params.modificationDate] - Modification date. Format: yyyyMMddHHmmss. Examples: >2018, 2017-2018, <201805. + * @param {String} [params.acl] - Filter entries for which a user has the provided permissions. Format: acl={user}:{permissions}. + * Example: acl=john:WRITE,WRITE_ANNOTATIONS will return all entries for which user john has both WRITE and WRITE_ANNOTATIONS + * permissions. Only study owners or administrators can query by this field. . + * @param {String} [params.release] - Release when it was created. + * @param {Number} [params.snapshot] - Snapshot value (Latest version of the entry in the specified release). + * @param {Boolean} [params.deleted = "false"] - Boolean to retrieve deleted entries. The default value is false. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + search(params) { + return this._get("workflows", null, null, null, "search", params); + } + + /** Update some workflow attributes + * @param {String} workflowId - Comma separated list workflow IDs or UUIDs up to a maximum of 100. + * @param {Object} [data] - body. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.include] - Fields included in the response, whole JSON path must be provided. + * @param {String} [params.exclude] - Fields excluded in the response, whole JSON path must be provided. + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @param {Boolean} [params.includeResult = "false"] - Flag indicating to include the created or updated document result in the response. + * The default value is false. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + update(workflowId, data, params) { + return this._post("workflows", workflowId, null, null, "update", data, params); + } + + /** Returns the acl of the workflows. If member is provided, it will only return the acl for the member. + * @param {String} workflows - Comma separated list workflow IDs or UUIDs up to a maximum of 100. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @param {String} [params.member] - User or group id. + * @param {Boolean} [params.silent = "false"] - Boolean to retrieve all possible entries that are queried for, false to raise an + * exception whenever one of the entries looked for cannot be shown for whichever reason. The default value is false. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + acl(workflows, params) { + return this._get("workflows", workflows, null, null, "acl", params); + } + + /** Delete workflows + * @param {String} workflows - Comma separated list workflow IDs or UUIDs up to a maximum of 100. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + delete(workflows, params) { + return this._delete("workflows", workflows, null, null, "delete", params); + } + + /** Get workflow information + * @param {String} workflows - Comma separated list sample IDs or UUIDs up to a maximum of 100. + * @param {Object} [params] - The Object containing the following optional parameters: + * @param {String} [params.include] - Fields included in the response, whole JSON path must be provided. + * @param {String} [params.exclude] - Fields excluded in the response, whole JSON path must be provided. + * @param {String} [params.study] - Study [[organization@]project:]study where study and project can be either the ID or UUID. + * @param {String} [params.version] - Comma separated list of workflow versions. 'all' to get all the workflow versions. Not supported if + * multiple workflow ids are provided. + * @param {Boolean} [params.deleted = "false"] - Boolean to retrieve deleted entries. The default value is false. + * @returns {Promise} Promise object in the form of RestResponse instance. + */ + info(workflows, params) { + return this._get("workflows", workflows, null, null, "info", params); + } + +} \ No newline at end of file diff --git a/src/core/clients/opencga/opencga-client.js b/src/core/clients/opencga/opencga-client.js index bb7cd4c83..58d788702 100644 --- a/src/core/clients/opencga/opencga-client.js +++ b/src/core/clients/opencga/opencga-client.js @@ -32,10 +32,10 @@ import Study from "./api/Study.js"; import User from "./api/User.js"; import Variant from "./api/Variant.js"; import VariantOperation from "./api/VariantOperation.js"; +import Workflow from "./api/Workflow.js"; import {CellBaseClient} from "../cellbase/cellbase-client.js"; import UtilsNew from "../../utils-new.js"; - export class OpenCGAClient { constructor(config) { @@ -202,6 +202,13 @@ export class OpenCGAClient { return this.clients.get("variantOperations"); } + workflows() { + if (!this.clients.has("workflows")) { + this.clients.set("workflows", new Workflow(this._config)); + } + return this.clients.get("workflows"); + } + ga4gh() { if (!this.clients.has("ga4gh")) { this.clients.set("ga4gh", new GA4GH(this._config)); @@ -258,6 +265,8 @@ export class OpenCGAClient { case "CLINICAL": case "CLINICAL_ANALYSIS": return this.clinical(); + case "WORKFLOW": + return this.workflows(); case "META": return this.meta(); case "ADMIN": @@ -369,13 +378,13 @@ export class OpenCGAClient { // opencgaClient object itself. // @returns {Promise} createSession() { - const _this = this; + // const _this = this; return new Promise((resolve, reject) => { // check that a session exists // TODO should we check the session has not expired? - if (_this._config.token) { - _this.users() - .info(_this._config.userId) + if (this._config.token) { + this.users() + .info(this._config.userId) .then(async response => { console.log("Creating session"); const session = { @@ -383,13 +392,13 @@ export class OpenCGAClient { }; try { session.user = response.getResult(0); - session.token = _this._config.token; + session.token = this._config.token; session.date = new Date().toISOString(); session.server = { - host: _this._config.host, - version: _this._config.version, + host: this._config.host, + version: this._config.version, }; - session.opencgaClient = _this; + session.opencgaClient = this; const userConfig = await this.updateUserConfig("IVA", { ...session.user.configs.IVA, lastAccess: new Date().getTime() @@ -409,9 +418,9 @@ export class OpenCGAClient { // Fetch authorised Projects and Studies console.log("Fetching projects and studies"); - _this.projects() + this.projects() .search({limit: 100}) - .then(async function (response) { + .then(async response => { try { for (const project of response.responses[0].results) { const projectIndex = session.projects.findIndex(proj => proj.fqn === project.fqn); @@ -430,15 +439,15 @@ export class OpenCGAClient { let acl = null; const admins = study.groups.find(g => g.id === "@admins"); if (admins.userIds?.includes(session.user.id)) { - acl = await _this.studies().acl(study.fqn, {}); + acl = await this.studies().acl(study.fqn, {}); } else { - acl = await _this.studies().acl(study.fqn, {member: session.user.id}); + acl = await this.studies().acl(study.fqn, {member: session.user.id}); } study.acl = acl.getResult(0)?.acl || []; // Fetch all the cohort console.log("Fetching cohorts"); - const cohortsResponse = await _this.cohorts() + const cohortsResponse = await this.cohorts() .search({study: study.fqn, exclude: "samples", limit: 100}); study.cohorts = cohortsResponse.responses[0].results .filter(cohort => !cohort.attributes?.IVA?.ignore); @@ -482,21 +491,39 @@ export class OpenCGAClient { } // Fetch the Disease Panels for each Study - console.log("Fetching disease panels"); - const panelPromises = []; + // console.log("Fetching disease panels"); + // const panelPromises = []; + // for (const study of studies) { + // const promise = this.panels() + // .search({ + // study: study, + // limit: 1000, + // include: "id,name,stats,source,genes.id,genes.name,genes.modeOfInheritance,genes.confidence,regions.id" + // }); + // panelPromises.push(promise); + // } + // const panelResponses = await Promise.all(panelPromises); + // for (let i = 0, t = 0; i < session.projects.length; i++) { + // for (let x = 0; x < session.projects[i].studies.length; x++, t++) { + // session.projects[i].studies[x].panels = panelResponses[t].getResults(); + // } + // } + + // Fetch the Workflows for each Study + console.log("Fetching Workflows"); + const workflowPromises = []; for (const study of studies) { - const promise = _this.panels() + const promise = this.workflows() .search({ study: study, limit: 1000, - include: "id,name,stats,source,genes.id,genes.name,genes.modeOfInheritance,genes.confidence,regions.id" }); - panelPromises.push(promise); + workflowPromises.push(promise); } - const panelResponses = await Promise.all(panelPromises); + const workflowResponses = await Promise.all(workflowPromises); for (let i = 0, t = 0; i < session.projects.length; i++) { for (let x = 0; x < session.projects[i].studies.length; x++, t++) { - session.projects[i].studies[x].panels = panelResponses[t].getResults(); + session.projects[i].studies[x].workflows = workflowResponses[t].getResults(); } } } @@ -517,8 +544,8 @@ export class OpenCGAClient { reject(new Error("An error getting user information")); }); } else { - console.error("No valid token:" + _this?._config?.token); - reject(new Error("No valid token:" + _this?._config?.token)); + console.error("No valid token:" + this?._config?.token); + reject(new Error("No valid token:" + this?._config?.token)); } }); } diff --git a/src/sites/iva/conf/config.js b/src/sites/iva/conf/config.js index 82dcb03b1..31b449305 100644 --- a/src/sites/iva/conf/config.js +++ b/src/sites/iva/conf/config.js @@ -26,7 +26,7 @@ const hosts = [ }, { id: "reference", - url: "https://test.app.zettagenomics.com/reference/opencga" + url: "https://test.app.zettagenomics.com/TASK-6445/opencga" }, ]; @@ -100,9 +100,8 @@ const CATALOG_NAVBAR_MENU = { // name: "Projects", // visibility: "public" // }, - { - name: "Browsers", + name: "Metadata", category: true, id: "cat-catalog", visibility: "public" @@ -151,8 +150,9 @@ const CATALOG_NAVBAR_MENU = { visibility: "public" }, { - id: "clinicalAnalysis", - name: "Clinical Analysis Browser", + name: "Clinical", + category: true, + id: "cat-clinical", visibility: "public" }, { @@ -160,10 +160,26 @@ const CATALOG_NAVBAR_MENU = { name: "Disease Panel Browser", visibility: "public" }, + { + id: "clinicalAnalysis", + name: "Clinical Analysis Browser", + visibility: "public" + }, { separator: true, visibility: "public" }, + { + name: "Analysis", + category: true, + id: "cat-analysis", + visibility: "public" + }, + { + id: "workflow", + name: "Workflow Browser", + visibility: "public" + }, { id: "job", name: "Job Browser", @@ -199,10 +215,16 @@ const SUITE = { {id: "getting-started", name: "Getting Started", tab: false, url: "#getting-started", icon: "fa fa-book"} ] }, - jobMonitor: { + fileExplorer: { visibility: "private" }, - fileExplorer: { + workflowManager: { + visibility: "private" + }, + customToolAnalysisExecutor: { + visibility: "private" + }, + jobMonitor: { visibility: "private" }, restApi: { @@ -347,6 +369,36 @@ const SUITE = { icon: "img/tools/icons/aggregation.svg", visibility: "public", submenu: [ + { + name: "Analysis Execution", + category: true, + visibility: "public" + }, + { + id: "tool-analysis", + name: "Tool Executor", + description: "", + icon: "", + visibility: "public" + }, + { + id: "custom-tool-builder", + name: "Custom Tool Builder", + description: "", + icon: "", + visibility: "public" + }, + { + id: "workflow-analysis", + name: "Workflow Executor", + description: "", + icon: "", + visibility: "public" + }, + { + separator: true, + visibility: "public" + }, { name: "Summary Stats", category: true, diff --git a/src/sites/iva/iva-app.js b/src/sites/iva/iva-app.js index b39681efd..ec4a62fd9 100644 --- a/src/sites/iva/iva-app.js +++ b/src/sites/iva/iva-app.js @@ -57,6 +57,11 @@ import "../../webcomponents/individual/individual-update.js"; import "../../webcomponents/cohort/cohort-browser.js"; import "../../webcomponents/job/job-browser.js"; import "../../webcomponents/job/job-view.js"; +import "../../webcomponents/job/analysis/tool-analysis.js"; +import "../../webcomponents/job/analysis/custom-tool-builder.js"; +import "../../webcomponents/workflow/workflow-browser.js"; +import "../../webcomponents/workflow/workflow-manager.js"; +import "../../webcomponents/workflow/analysis/workflow-analysis.js"; import "../../webcomponents/clinical/analysis/mutational-signature-analysis.js"; import "../../webcomponents/variant/analysis/gwas-analysis.js"; import "../../webcomponents/variant/analysis/sample-variant-stats-analysis.js"; @@ -81,6 +86,7 @@ import "../../webcomponents/clinical/analysis/rd-tiering-analysis.js"; import "../../webcomponents/clinical/analysis/hrdetect-analysis.js"; import "../../webcomponents/clinical/clinical-analysis-create.js"; import "../../webcomponents/file/file-manager.js"; +import "../../webcomponents/file/file-data-manager.js"; import "../../webcomponents/job/job-monitor.js"; import "../../webcomponents/loading-spinner.js"; import "../../webcomponents/organization/admin/organization-admin.js"; @@ -170,6 +176,7 @@ class IvaApp extends LitElement { "account", "projects", "file-manager", + "file-data-manager", "beacon", "project", "file", @@ -195,6 +202,9 @@ class IvaApp extends LitElement { "protein", "variant-browser", "job", + "workflow", + "workflow-manager", + "workflow-analysis", "cat-browser", "cat-analysis", "cat-clinical", @@ -229,6 +239,8 @@ class IvaApp extends LitElement { "alignment-stats", "coverage-index", "job-view", + "tool-analysis", + "custom-tool-builder", "rga", "disease-panel", "diseasePanelUpdate", @@ -928,6 +940,9 @@ class IvaApp extends LitElement { case "#diseasePanelUpdate": this.diseasePanelId = hashQuery; break; + case "#workflow-analysis": + this.workflowId = hashQuery; + break; } // this.requestUpdate(); } @@ -1756,6 +1771,43 @@ class IvaApp extends LitElement { ` : nothing} + ${this.config.enabledComponents.workflow ? html` +
+ + +
+ ` : nothing} + + ${this.config.enabledComponents["workflow-manager"] ? html` + +
+ + +
+ ` : nothing} + + ${this.config.enabledComponents["workflow-analysis"] ? html` + +
+ + +
+ ` : nothing} + + ${this.config.enabledComponents["cat-browser"] ? html` +
+ + +
+ ` : nothing} ${this.config.enabledComponents["cat-analysis"] ? html`
@@ -1816,19 +1868,23 @@ class IvaApp extends LitElement {
` : nothing} - ${this.config.enabledComponents["sample-variant-stats"] ? html` -
- - -
- ` : nothing} + ${this.config.enabledComponents["sample-variant-stats"] ? html` + +
+ + +
+ ` : nothing} - ${this.config.enabledComponents["cohort-variant-stats"] ? html` -
- -
- ` : nothing} + ${this.config.enabledComponents["cohort-variant-stats"] ? html` + +
+ + +
+ ` : nothing} ${this.config.enabledComponents["eligibility"] ? html`
@@ -1876,32 +1932,35 @@ class IvaApp extends LitElement {
` : nothing} - ${this.config.enabledComponents["sample-qc"] ? html` -
- - -
- ` : nothing} + ${this.config.enabledComponents["sample-qc"] ? html` + +
+ + +
+ ` : nothing} - ${this.config.enabledComponents["individual-qc"] ? html` -
- - -
- ` : nothing} + ${this.config.enabledComponents["individual-qc"] ? html` + +
+ + +
+ ` : nothing} - ${this.config.enabledComponents["family-qc"] ? html` -
- - -
- ` : nothing} + ${this.config.enabledComponents["family-qc"] ? html` + +
+ + +
+ ` : nothing} ${this.config.enabledComponents["plink"] ? html` ` : nothing} - ${this.config.enabledComponents["gwas"] ? html` -
- - -
- ` : nothing} + ${this.config.enabledComponents["gwas"] ? html` + +
+ + +
+ ` : nothing} ${this.config.enabledComponents["rd-tiering"] ? html`
@@ -1978,6 +2038,14 @@ class IvaApp extends LitElement {
` : nothing} + ${this.config.enabledComponents["file-data-manager"] ? html` +
+ + +
+ ` : nothing} + ${this.config.enabledComponents.settings ? html`
@@ -2015,16 +2083,34 @@ class IvaApp extends LitElement {
` : nothing} - ${this.config.enabledComponents["job-view"] ? html` - -
- - -
- ` : nothing} + ${this.config.enabledComponents["job-view"] ? html` + +
+ + +
+ ` : nothing} + + ${this.config.enabledComponents["tool-analysis"] ? html` + +
+ + +
+ ` : nothing} + + ${this.config.enabledComponents["custom-tool-builder"] ? html` + +
+ + +
+ ` : nothing} ${this.config.enabledComponents["organization-admin"] ? html` diff --git a/src/webcomponents/cohort/cohort-grid.js b/src/webcomponents/cohort/cohort-grid.js index 913390c39..2d031e996 100644 --- a/src/webcomponents/cohort/cohort-grid.js +++ b/src/webcomponents/cohort/cohort-grid.js @@ -235,6 +235,7 @@ export default class CohortGrid extends LitElement { include: "id,creationDate,status,type,numSamples,annotationSets", ...this.query }; + // Store the current filters this.lastFilters = {...this.filters}; this.opencgaSession.opencgaClient.cohorts() diff --git a/src/webcomponents/commons/analysis/analysis-utils.js b/src/webcomponents/commons/analysis/analysis-utils.js index 5d24d7ad6..0c03f556b 100644 --- a/src/webcomponents/commons/analysis/analysis-utils.js +++ b/src/webcomponents/commons/analysis/analysis-utils.js @@ -14,6 +14,13 @@ export default class AnalysisUtils { // }; // } + static extToolsDocker(opencgaSession) { + return { + id: "opencb/opencga-ext-tools", + version: opencgaSession.opencga.version + }; + } + static submit(id, promise, context) { return promise .then(response => { @@ -145,6 +152,7 @@ export default class AnalysisUtils { { title: "Job Info", display: { + className: "p-2", visible: config.isJob !== undefined ? config.isJob : true, }, elements: [ diff --git a/src/webcomponents/commons/catalog-grid-formatter.js b/src/webcomponents/commons/catalog-grid-formatter.js index 019df3577..79c6e78fb 100644 --- a/src/webcomponents/commons/catalog-grid-formatter.js +++ b/src/webcomponents/commons/catalog-grid-formatter.js @@ -192,6 +192,19 @@ export default class CatalogGridFormatter { return "-"; } + static modifiedAndCreateDateFormatter(value, row) { + let result = "-"; + if (row) { + result = ` +
+ ${UtilsNew.dateFormatter(row.modificationDate)} + ${UtilsNew.dateFormatter(row.creationDate)} +
+ `; + } + return result; + } + static caseFormatter(clinicalAnalysisArray, row, individualId, opencgaSession) { if (clinicalAnalysisArray?.length > 0) { let result = ""; diff --git a/src/webcomponents/commons/data-list.js b/src/webcomponents/commons/data-list.js new file mode 100644 index 000000000..f450a8eeb --- /dev/null +++ b/src/webcomponents/commons/data-list.js @@ -0,0 +1,542 @@ +/* + * Copyright 2015-2016 OpenCB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {html, LitElement, nothing} from "lit"; +import GridCommons from "./grid-commons.js"; +import UtilsNew from "../../core/utils-new.js"; +import LitUtils from "./utils/lit-utils.js"; + +export default class DataList extends LitElement { + + static LIST_MODE = "LIST"; + static GRID_MODE = "GRID"; + + constructor() { + super(); + + this.#init(); + } + + createRenderRoot() { + return this; + } + + static get properties() { + return { + data: { + type: Array + }, + search: { + type: String + }, + mode: { + type: String + }, + active: { + type: Boolean + }, + config: { + type: Object + } + }; + } + + #init() { + this._prefix = UtilsNew.randomString(8); + this._data = []; + this.data = []; + this.mode = DataList.LIST_MODE; + this.active = true; + this.htmlTableId = this._prefix + "TableHtmlId"; + + this.searchField = ""; + this.sortById = "none"; + + this.groupById = "none"; + this.groupByResult = {}; + this.groupByResultValues = []; + + this._config = this.getDefaultConfig(); + } + + update(changedProperties) { + if (changedProperties.has("data")) { + this.dataObserver(); + } + if (changedProperties.has("mode") || + changedProperties.has("config")) { + this.propertyObserver(); + } + super.update(changedProperties); + } + + updated(changedProperties) { + // If the search property is changed, we must update the input field and we need the DOM to be ready + if (changedProperties.has("search")) { + this.querySelector("#" + this._prefix + "InputSearch").value = this.search; + this.onSearch({currentTarget: {value: this.search}}); + } + if (changedProperties.size > 0 && this.active) { + this.renderTable(); + } + } + + dataObserver() { + this._data = JSON.parse(JSON.stringify(this.data)); + } + + modeObserver(e, mode) { + this.mode = mode; + this.renderTable(); + } + + propertyObserver() { + // With each property change we must be updated config and create the columns again. No extra checks are needed. + this._config = { + ...this.getDefaultConfig(), + ...this.config, + display: { + ...this.getDefaultConfig().display, + ...this.config.display, + }, + search: { + ...this.getDefaultConfig().search, + ...this.config.search, + }, + sortBy: { + ...this.getDefaultConfig().sortBy, + ...this.config.sortBy, + }, + groupBy: { + ...this.getDefaultConfig().groupBy, + ...this.config.groupBy, + }, + table: { + ...this.getDefaultConfig().table, + ...this.config.table, + options: { + ...this.getDefaultConfig().table.options, + ...this.config.table.options + }, + columns: this.config.table?.columns || this.getDefaultConfig().table.columns + }, + grid: { + ...this.getDefaultConfig().grid, + ...this.config.grid, + options: { + ...this.getDefaultConfig().grid.options, + ...this.config.grid.options + }, + render: this.config.grid?.render || this.getDefaultConfig().grid.render + }, + }; + + if (this._config.table?.checkbox) { + this._config.table.columns.splice(this._config.table?.checkboxIndex || 0, 0, { + field: "state", + checkbox: true, + align: "center", + valign: "middle" + }); + } + this.gridCommons = new GridCommons(this.htmlTableId, this, this._config); + this.renderTable(); + } + + onSearch(e) { + // Save the search value + this.searchField = e.currentTarget.value; + + // 1. Filter the data using the original data! + if (e.currentTarget.value === "none") { + this._data = JSON.parse(JSON.stringify(this.data)); + } else { + this._data = this.#search(this.data, e.currentTarget.value); + } + + // 2. If we are sorting by some field, we need to re-sort the data to reflect the new order + if (this.sortById !== "none") { + const sortByOption = this._config.sortBy.options.find(option => option.id === this.sortById); + this._data = this.#sortData(this._data, sortByOption); + } + + // 3. If we are grouping by some field, we need to re-group the data to reflect the new order + if (this.groupById !== "none") { + // Note: this calls to renderTable() again + this.onGroupBy({currentTarget: {value: this.groupById}}); + } else { + this.renderTable(); + } + } + + // Private method that filters the data using the provided value + #search(data, value) { + return data.filter(item => { + for (const field of this._config.search.fields) { + if (this._config.search.ignoreCase) { + if (Array.isArray(item[field])) { + if (item[field].join().toLowerCase().includes(value.toLowerCase())) { + return true; + } + } else { + if (item[field]?.toLowerCase().includes(value.toLowerCase())) { + return true; + } + } + } else { + if (Array.isArray(item[field])) { + if (item[field]?.join()?.includes(value)) { + return true; + } + } else { + if (item[field]?.includes(value)) { + return true; + } + } + } + } + }); + } + + onSortBy(e) { + // Save the selected sort by field + this.sortById = e.currentTarget.value; + + if (e.currentTarget.value === "none") { + // Reset the data + this._data = JSON.parse(JSON.stringify(this.data)); + if (this.searchField) { + this._data = this.#search(this.data, this.searchField); + } + } else { + // Get the selected option + const sortByOption = this._config.sortBy.options.find(option => option.id === e.currentTarget.value); + // Set default order to 'asc' + if (!sortByOption.order) { + sortByOption.order = "asc"; + } + // Sort by 'id' field using the already filtered data, no need to filter again + this._data = this.#sortData(this._data, sortByOption); + } + + // If we are grouping by some field, we need to re-group the data to reflect the new order + if (this.groupById !== "none") { + // Note: this calls to renderTable() again + this.onGroupBy({currentTarget: {value: this.groupById}}); + } else { + this.renderTable(); + } + } + + onSortByClear() { + const dropdown = document.getElementById(this._prefix + "SortBy"); + dropdown.value = "none"; + this.onSortBy({currentTarget: dropdown}); + } + + #sortData(data, sortByOption) { + return data.sort((a, b) => { + if (a[sortByOption.id] > b[sortByOption.id]) { + return sortByOption.order === "asc" ? 1 : -1; + } else if (a[sortByOption.id] < b[sortByOption.id]) { + return sortByOption.order === "asc" ? -1 : 1; + } else { + return 0; + } + }); + } + + onGroupBy(e) { + // Save the selected groupBy field + this.groupById = e.currentTarget.value; + + if (e.currentTarget.value === "none") { + this.groupByResult = {}; + this.groupByResultValues = []; + } else { + const groupByOption = this._config.groupBy.options.find(option => option.id === e.currentTarget.value); + this.groupByResult = this._data.reduce((result, currentValue) => { + (result[currentValue[groupByOption.id]] = result[currentValue[groupByOption.id]] || []) + .push(currentValue); + return result; + }, {}); + this.groupByResultValues = groupByOption.values?.length > 0 ? groupByOption.values : Object.keys(this.groupByResult); + } + this.renderTable(); + } + + onGroupByClear() { + const dropdown = document.getElementById(this._prefix + "GroupBy"); + dropdown.value = "none"; + this.onGroupBy({currentTarget: dropdown}); + } + + renderToolbar() { + const float = this._config?.display?.float === "left" ? "float-start" : "float-end"; + return html` + + `; + } + + async renderTable() { + // This renders the GRID mode automatically, since the render() method calls to the JS function renderUngroupedAndGroupByWithGrid() + this.requestUpdate(); + await this.updateComplete; + + // LIST mode is implemented using Bootstrap Table, so we need to call to these methods + if (this.mode.toUpperCase() === DataList.LIST_MODE) { + if (this.groupByResultValues?.length === 0) { + this.renderUngroupedWithLists(); + } else { + this.renderGroupByWithLists(); + } + } + } + + renderUngroupedWithLists() { + this.table = $("#" + this.htmlTableId); + this.table.bootstrapTable("destroy"); + this.table.bootstrapTable({ + uniqueId: this._config.table?.uniqueId || "id", + data: this._data, + columns: this._config.table.columns, + + // Add default configuration + ...this._config.table.options, + + detailView: this._config.detailView, + gridContext: this, + loadingTemplate: () => GridCommons.loadingFormatter(), + onClickRow: (row, selectedElement) => LitUtils.dispatchCustomEvent(this, "clickrow", row, selectedElement), + // onClickRow: (row, selectedElement) => this.gridCommons.onClickRow(row.id, row, selectedElement), + onDblClickRow: (row, element) => LitUtils.dispatchCustomEvent(this, "doubleclickrow", row, element), + onCheck: row => this.gridCommons.onCheck(row.id, row), + onUncheck: row => this.gridCommons.onUncheck(row.id, row), + }); + + // Show/Hide table header + this.gridCommons.hideHeader(!this._config.table?.showHeader); + } + + renderGroupByWithLists() { + for (const value of this.groupByResultValues) { + const valueHtmlTableId = this._prefix + value + "TableHtmlId"; + this.table = $("#" + valueHtmlTableId); + this.table.bootstrapTable("destroy"); + this.table.bootstrapTable({ + uniqueId: this._config.table?.uniqueId || "id", + data: this.groupByResult[value], + columns: this._config.table.columns, + + // Add default configuration + ...this._config.table.options, + + detailView: this._config.detailView, + gridContext: this, + loadingTemplate: () => GridCommons.loadingFormatter(), + onClickRow: (row, selectedElement) => LitUtils.dispatchCustomEvent(this, "clickrow", row, selectedElement), + onDblClickRow: (row, element) => LitUtils.dispatchCustomEvent(this, "doubleclickrow", row, element), + onCheck: row => this.gridCommons.onCheck(row.id, row), + onUncheck: row => this.gridCommons.onUncheck(row.id, row), + }); + + const header = this.querySelector(`#${valueHtmlTableId} thead`); + if (header) { + if (!this._config.table?.showHeader) { + header.style.display = "none"; + // this.context.querySelector(`#${this.gridId} tbody tr:first-child`).style.borderTopWidth = "1px"; + } else { + header.style.display = ""; + } + } + } + } + + renderUngroupedAndGroupByWithGrid(value) { + // 1. Get the data for the selected groupBy value, if not provided, use the ungrouped data + const localData = value ? this.groupByResult[value] || [] : this._data; + + // 2. If there is no data, show a message + if (localData.length === 0) { + return html` +
+
No data available
+
+ `; + } + + // 3. Calculate the number of columns and rows + const numColumns = this._config.grid?.options?.columns || 2; + const numRows = Math.ceil(localData.length / numColumns); + const columnWidth = 12 / numColumns; + + const htmlResult = []; + for (let i = 0; i < numRows; i ++) { + // 4. Prepare the row data + const row = []; + for (let j = 0; j < numColumns; j++) { + if (i * numColumns + j < localData.length) { + row.push(localData[i * numColumns + j]); + } + } + + // 5. Render the row + htmlResult.push(html` +
+ ${row.map(r => html` +
+
+ ${this._config.grid.render(r)} +
+
+ `)} +
+ `); + } + + return html` +
+ ${htmlResult} +
+ `; + } + + render() { + return html` + ${this.renderToolbar()} + + ${this.mode === DataList.LIST_MODE ? html` + + ${this.groupByResultValues?.length === 0 ? html` +
+
+
+ ` : html` + ${this.groupByResultValues?.map(value => html` +
+

${value}

+
+
+ `)} + `} + ` : nothing} + + ${this.mode === DataList.GRID_MODE ? html` + ${this.groupByResultValues?.length === 0 ? this.renderUngroupedAndGroupByWithGrid() : html` + ${this.groupByResultValues?.map(value => html` +
+

${value}

+ ${this.renderUngroupedAndGroupByWithGrid(value)} +
+ `)} + `} + ` : nothing} + `; + } + + getDefaultConfig() { + return { + display: { + classes: "shadow bg-body-tertiary rounded", + style: "", + float: "left", + }, + search: { + fields: ["id", "name", "description"], + placeholder: "Search ...", + ignoreCase: true + }, + table: { + uniqueId: "id", + showHeader: false, + checkbox: false, + checkboxIndex: 0, + options: { + classes: "table table-hover table-borderless", + theadClasses: "table-light", + buttonsClass: "light", + iconsPrefix: GridCommons.GRID_ICONS_PREFIX, + icons: GridCommons.GRID_ICONS, + pagination: false, + pageSize: 100, + pageList: [100], + detailView: false, + checkbox: true + } + }, + grid: { + options: { + columns: 3, + rowClass: "g-2", + cellClass: "p-2" + }, + render: data => { + return html` +
+ `; + } + } + }; + } + +} + +customElements.define("data-list", DataList); diff --git a/src/webcomponents/commons/filters/catalog-distinct-autocomplete.js b/src/webcomponents/commons/filters/catalog-distinct-autocomplete.js index d65a1bd8f..6f46fdeee 100644 --- a/src/webcomponents/commons/filters/catalog-distinct-autocomplete.js +++ b/src/webcomponents/commons/filters/catalog-distinct-autocomplete.js @@ -87,6 +87,7 @@ export default class CatalogDistinctAutocomplete extends LitElement { "JOB": this.opencgaSession.opencgaClient.jobs(), "FILE": this.opencgaSession.opencgaClient.files(), "COHORT": this.opencgaSession.opencgaClient.cohorts(), + "WORKFLOW": this.opencgaSession.opencgaClient.workflows(), }; const page = params?.data?.page || 1; diff --git a/src/webcomponents/commons/filters/catalog-search-autocomplete.js b/src/webcomponents/commons/filters/catalog-search-autocomplete.js index d2ac72c21..f114b9700 100644 --- a/src/webcomponents/commons/filters/catalog-search-autocomplete.js +++ b/src/webcomponents/commons/filters/catalog-search-autocomplete.js @@ -204,6 +204,19 @@ export default class CatalogSearchAutocomplete extends LitElement { include: "id,name,format,size,path", } }, + "WORKFLOW": { + searchField: "id", + placeholder: "Start typing", + // client: this.opencgaSession.opencgaClient.workflows(), + fetch: filters => this.opencgaSession.opencgaClient.workflows().search(filters), + fields: item => ({ + id: item.id, + name: item.name + }), + query: { + include: "id,name" + } + }, "DIRECTORY": { searchField: "path", placeholder: "eg. /data/platinum-grch38...", diff --git a/src/webcomponents/commons/forms/data-form.js b/src/webcomponents/commons/forms/data-form.js index 7177f8905..d325d2d27 100644 --- a/src/webcomponents/commons/forms/data-form.js +++ b/src/webcomponents/commons/forms/data-form.js @@ -457,10 +457,10 @@ export default class DataForm extends LitElement {
${this._getVisibleSections() .map((section, index) => html` -
- ${this._createSection(section)} -
- `)} +
+ ${this._createSection(section)} +
+ `)}
`; } else { @@ -848,12 +848,16 @@ export default class DataForm extends LitElement { // Josemi 20220202 NOTE: this function was prev called _createInputTextElement _createInputElement(element, type, section) { - const value = this.getValue(element.field) || this._getDefaultValue(element, section); + let value = this.getValue(element.field) || this._getDefaultValue(element, section); const disabled = this._getBooleanValue(element.display?.disabled, false, element); const [min = undefined, max = undefined] = element.allowedValues || []; const step = element.step || "1"; const rows = element.display && element.display.rows ? element.display.rows : 1; + // if (Array.isArray(value)) { + // value = value.join(","); + // } + const content = html` ${elem} ${separators[index] ? `
${separators[index]}
` : ""} `) - .join("") - } + .join("") + } `; break; @@ -1587,7 +1591,7 @@ export default class DataForm extends LitElement {
- ${element.display.search.render(data, object => this.onObjectChange(element, object, {action: "AUTOCOMPLETE"}))} + ${element.display.search.render(data, object => this.onObjectChange(element, object, {action: "AUTOCOMPLETE"}))}
`; @@ -1745,7 +1749,7 @@ export default class DataForm extends LitElement {
+ class="ms-2 ps-3 border-start border-2 border-new d-${index === this.editOpen ? "block" : "none"}"> ${this._createObjectElement(_element)}
`; - }) + }) } @@ -2287,7 +2291,7 @@ export default class DataForm extends LitElement {