Skip to content

Commit

Permalink
Workflow Executions: Share results (#895)
Browse files Browse the repository at this point in the history
* Add migration to add shared_with_namespace column to workflow executions

* Add checkbox to workflow execution creation form

* Update workflow execution creation tests

* Define new scope for returning shared workflow executions

* Add shared workflow executions to list of WEs for a project

* Create new policy for getting shared workflow executions

* Update load_workflows to add workflows shared to the Project

* Remove scope for getting user and shared workflows

* Update system tests since we added new fixtures

* Add workflow execution section in Groups sidebar

* Create new workflow execution controller for Groups

* Add new workflow execution controller test file

* Create new policy for viewing workflow executions in Groups

* Add new scope for finding shared WEs in a Group

* Add an index page for group workflow executions

* Update translations

* Update group routes

* Create test for new group_shared scope

* Fix errors for calling groups workflow executions page

* Add test for calling new workflow execution page

* Add workflow execution table toGroups

* Update translations

* Fix bug where namespace.project is called on a group

* Include shared workflow executions in the show route

* Update tests for project workflow execution controller

* Remove changes relating to group to move to new branch

* Fix rubocop warning

* Add namespace_type variable to translations

* Modify checkbox prompt to use proper namespace type

* Remove action buttons from show page

* Fix workflow executions submissions tests

* Fix format of class list

* Allow user actions for shared workflows they submitted

* Update description for workflow index page

* Add UI tests to ensure shared workflows are displayed properly

* Simplify rendering row action logic

* Hide prompt when creating automated workflow executions

* Use namespace_id to get namespace_type

* Update nextflow_component_test.rb

* Use nested translations for shared_with_namespace

* Update component test with proper namespace ids

* Update submissions_test with new translations

* Normalize translations

* Modify nextflow component to not show empty div

* Update workflow execution tests to be less flakey

* Update table to render Actions cell consistently

* Fix bug with exporting shared workflow executions

* Fix table component actions cell logic

* Update workflow execution policies and tests

* Remove unnecessary shared_with_namespace check

* Include checkbox when using an automatable pipeline

* Add new fixture files for export tests

* Create fixtures for export testing

* Fix policy bug when user is not submitter or bot

* Add shared workflow tests to data_exports_test

* Fix bug where incorrect redirect path is used

* Simplify redirect_to_project method
  • Loading branch information
malchua authored Feb 10, 2025
1 parent 639bfc9 commit 53d0e3e
Show file tree
Hide file tree
Showing 31 changed files with 814 additions and 101 deletions.
17 changes: 17 additions & 0 deletions app/components/nextflow_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,23 @@
t(:"components.nextflow.email_notification"),
class: "mr-2 text-sm font-medium text-slate-900 dark:text-white" %>
</div>
<%# do not render checkbox for an automated workflow execution %>
<% if @namespace_type && !@samples.empty? %>
<div class="flex items-center h-5 mb-4">
<%= workflow.check_box :shared_with_namespace,
{
checked:
instance.present? && instance["shared_with_namespace"],
class:
"w-4 h-4 mr-2.5 text-primary-600 bg-slate-100 border-slate-300 rounded focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-slate-800 focus:ring-2 dark:bg-slate-700 dark:border-slate-600",
} %>
<%= workflow.label :shared_with_namespace,
t(
:"components.nextflow.shared_with.#{@namespace_type.downcase}",
),
class: "mr-2 text-sm font-medium text-slate-900 dark:text-white" %>
</div>
<% end %>
<% end %>

<button
Expand Down
12 changes: 9 additions & 3 deletions app/components/nextflow_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ class NextflowComponent < Component
attr_reader :schema, :url, :workflow, :metadata_fields, :samples, :namespace_id, :instance

# rubocop:disable Metrics/ParameterLists
def initialize(url:, samples:, workflow:, fields:, namespace_id:,
allowed_to_update_samples: true, instance: nil)
def initialize(url:, samples:, workflow:, fields:, namespace_id:, allowed_to_update_samples: true, instance: nil)
@samples = samples
@namespace_id = namespace_id
@url = url
@workflow = workflow
@metadata_fields = fields
@allowed_to_update_samples = allowed_to_update_samples
@instance = instance
@namespace_type = namespace_type(namespace_id)
end

# rubocop:enable Metrics/ParameterLists

def namespace_type(namespace_id)
namespace = Namespace.find_by(id: namespace_id)
return unless namespace

namespace.type
end
end
52 changes: 27 additions & 25 deletions app/components/workflow_executions/table_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -145,31 +145,33 @@
tag: 'td',
classes: class_names('px-3 py-3 sticky right-0 bg-white dark:bg-slate-800 z-10 space-x-2')
) do %>
<% if workflow_execution.cancellable? && @row_actions.key?(:cancel) %>
<%= link_to(
t(:"workflow_executions.index.actions.cancel_button"),
cancel_path(workflow_execution),
data: {
turbo_stream: true,
turbo_method: :put,
turbo_confirm: t(:"workflow_executions.index.actions.cancel_confirm"),
},
class:
"font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline cursor-pointer",
) %>
<% end %>
<% if workflow_execution.deletable? && @row_actions.key?(:destroy) %>
<%= link_to(
t(:"workflow_executions.index.actions.delete_button"),
individual_path(workflow_execution),
data: {
turbo_stream: true,
turbo_method: :delete,
turbo_confirm: t(:"workflow_executions.index.actions.delete_confirm"),
},
class:
"font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline cursor-pointer",
) %>
<% if !workflow_execution.shared_with_namespace || @namespace.nil? %>
<% if workflow_execution.cancellable? && @row_actions.key?(:cancel) %>
<%= link_to(
t(:"workflow_executions.index.actions.cancel_button"),
cancel_path(workflow_execution),
data: {
turbo_stream: true,
turbo_method: :put,
turbo_confirm: t(:"workflow_executions.index.actions.cancel_confirm"),
},
class:
"font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline cursor-pointer",
) %>
<% end %>
<% if workflow_execution.deletable? && @row_actions.key?(:destroy) %>
<%= link_to(
t(:"workflow_executions.index.actions.delete_button"),
individual_path(workflow_execution),
data: {
turbo_stream: true,
turbo_method: :delete,
turbo_confirm: t(:"workflow_executions.index.actions.delete_confirm"),
},
class:
"font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline cursor-pointer",
) %>
<% end %>
<% end %>
<% end %>
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion app/components/workflow_executions/table_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def cancel_path(workflow_execution)
end

def individual_path(workflow_execution)
if @namespace
if @namespace && @namespace.type == 'Project'
namespace_project_workflow_execution_path(
@namespace.parent,
@namespace.project,
Expand Down
5 changes: 2 additions & 3 deletions app/controllers/data_exports_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,10 @@ def redirect_to_project

def redirect_to_workflow_execution
workflow_execution = WorkflowExecution.find_by(id: params['identifier'])
submitter = workflow_execution.submitter
if submitter.user_type == 'human'
if workflow_execution.submitter == current_user
redirect_to workflow_execution_path(workflow_execution)
else
namespace = Namespace.find_by(puid: submitter.first_name)
namespace = workflow_execution.namespace
redirect_to namespace_project_workflow_execution_path(namespace.parent, namespace.project, workflow_execution)
end
end
Expand Down
11 changes: 9 additions & 2 deletions app/controllers/projects/workflow_executions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@ def namespace
end

def workflow_execution
@workflow_execution = WorkflowExecution.find_by!(id: params[:id], submitter: @project.namespace.automation_bot)
@workflow_execution = WorkflowExecution.find_by(
id: params[:id],
submitter: @project.namespace.automation_bot,
shared_with_namespace: false
) || WorkflowExecution.find_by(id: params[:id], namespace:, shared_with_namespace: true)

raise ActiveRecord::RecordNotFound if @workflow_execution.nil?
end

def workflow_execution_update_params
params.require(:workflow_execution).permit(:name)
end

def load_workflows
authorized_scope(WorkflowExecution, type: :relation, as: :automated, scope_options: { project: @project })
authorized_scope(WorkflowExecution, type: :relation, as: :automated_and_shared,
scope_options: { project: @project })
end

def current_page
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ def pipeline_selection
end

def create
@namespace = Namespace.find_by(id: @namespace_id)
fields_for_namespace(
namespace: Namespace.find_by(id: @namespace_id),
namespace: @namespace,
show_fields: true
)
render status: :ok
Expand Down
1 change: 1 addition & 0 deletions app/controllers/workflow_executions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def workflow_execution_params_attributes
:namespace_id,
:update_samples,
:email_notification,
:shared_with_namespace,
{ metadata: {},
workflow_params: {},
samples_workflow_executions_attributes: samples_workflow_execution_params_attributes }
Expand Down
65 changes: 39 additions & 26 deletions app/policies/workflow_execution_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,13 @@ def project_automation_bot?(user)
User.user_types[user.user_type] == User.user_types[:project_automation_bot]
end

def destroy? # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
unless project_automation_bot?(user)
return true if record.submitter.id == user.id
return true if Member::AccessLevel.manageable.include?(effective_access_level)
end
def destroy? # rubocop:disable Metrics/AbcSize
return true if record.submitter.id == user.id

# submitted by automation bot and user has managable access
if (record.namespace.type == Namespaces::ProjectNamespace.sti_name) &&
(record.submitter.id == record.namespace.automation_bot.id) &&
(record.namespace.automation_bot.id == user.id) &&
Member::AccessLevel.manageable.include?(effective_access_level(record.namespace.automation_bot))
Member::AccessLevel.manageable.include?(effective_access_level)
return true
end

Expand All @@ -40,18 +37,18 @@ def destroy? # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
end

def read? # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
unless project_automation_bot?(user)
return true if record.submitter.id == user.id
return true if effective_access_level(user) > Member::AccessLevel::NO_ACCESS
end
return true if record.submitter.id == user.id

# submitted by automation bot and user has access
if (record.namespace.type == Namespaces::ProjectNamespace.sti_name) &&
(record.submitter.id == record.namespace.automation_bot.id) &&
(record.namespace.automation_bot.id == user.id) &&
(effective_access_level(record.namespace.automation_bot) > Member::AccessLevel::NO_ACCESS)
record.namespace.automation_bot && (record.submitter.id == record.namespace.automation_bot.id) &&
(effective_access_level > Member::AccessLevel::NO_ACCESS)
return true
end

# shared by submitter to namespace
return true if record.shared_with_namespace && (effective_access_level > Member::AccessLevel::NO_ACCESS)

details[:id] = record.id
false
end
Expand All @@ -64,16 +61,13 @@ def create?
false
end

def cancel? # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
unless project_automation_bot?(user)
return true if record.submitter.id == user.id
return true if Member::AccessLevel.manageable.include?(effective_access_level)
end
def cancel? # rubocop:disable Metrics/AbcSize
return true if record.submitter.id == user.id

# submitted by automation bot and user has managable access
if (record.namespace.type == Namespaces::ProjectNamespace.sti_name) &&
(record.submitter.id == record.namespace.automation_bot.id) &&
(record.namespace.automation_bot.id == user.id) &&
Member::AccessLevel.manageable.include?(effective_access_level(record.namespace.automation_bot))
record.namespace.automation_bot && (record.submitter.id == record.namespace.automation_bot.id) &&
Member::AccessLevel.manageable.include?(effective_access_level)
return true
end

Expand All @@ -82,17 +76,29 @@ def cancel? # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
false
end

def edit?
def edit? # rubocop:disable Metrics/AbcSize
return true if record.submitter.id == user.id
return true if effective_access_level >= Member::AccessLevel::ANALYST

# submitted by automation bot and user is analyst or higher
if (record.namespace.type == Namespaces::ProjectNamespace.sti_name) &&
record.namespace.automation_bot && (record.submitter.id == record.namespace.automation_bot.id) &&
(effective_access_level >= Member::AccessLevel::ANALYST)
return true
end

details[:id] = record.id
false
end

def update?
def update? # rubocop:disable Metrics/AbcSize
return true if record.submitter.id == user.id
return true if effective_access_level >= Member::AccessLevel::ANALYST

# submitted by automation bot and user is analyst or higher
if (record.namespace.type == Namespaces::ProjectNamespace.sti_name) &&
record.namespace.automation_bot && (record.submitter.id == record.namespace.automation_bot.id) &&
(effective_access_level >= Member::AccessLevel::ANALYST)
return true
end

details[:id] = record.id
false
Expand All @@ -109,4 +115,11 @@ def update?

relation.where(submitter_id: user.id)
end

scope_for :relation, :automated_and_shared do |relation, options|
project = options[:project]

relation.where(submitter: project.namespace.automation_bot)
.or(relation.where(namespace_id: project.namespace.id, shared_with_namespace: true))
end
end
2 changes: 1 addition & 1 deletion app/services/data_exports/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def authorized_export_samples(namespace, sample_ids)
def authorized_export_project_workflows
project_namespace = Namespace.find(params['export_parameters']['namespace_id'])
authorize! project_namespace, to: :export_data?
authorized_scope(WorkflowExecution, type: :relation, as: :automated,
authorized_scope(WorkflowExecution, type: :relation, as: :automated_and_shared,
scope_options: { project: project_namespace.project })
.where(id: params['export_parameters']['ids'])
end
Expand Down
5 changes: 4 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,9 @@ en:
nextflow:
email_notification: Receive an email notification when your analysis has completed?
required: Required
shared_with:
group: Share results with Group members?
project: Share results with Project members?
update_samples: Update samples with analysis results
pagination:
next: Next
Expand Down Expand Up @@ -1617,7 +1620,7 @@ en:
search:
placeholder: Filter by ID or name
select_all_button: Select All
subtitle: These are the workflow executions that have been automatically launched for this project
subtitle: These are the workflow executions that have been automatically launched for this project, along with workflow executions that have been shared by any of its members.
title: Workflow Executions
show:
cancel_button: Cancel
Expand Down
5 changes: 4 additions & 1 deletion config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,9 @@ fr:
nextflow:
email_notification: Receive an email notification when your analysis has completed?
required: Required
shared_with:
group: Share results with Group members?
project: Share results with Project members?
update_samples: Update samples with analysis results
pagination:
next: Next
Expand Down Expand Up @@ -1617,7 +1620,7 @@ fr:
search:
placeholder: Filter by ID or name
select_all_button: Select All
subtitle: These are the workflow executions that have been automatically launched for this project
subtitle: These are the workflow executions that have been automatically launched for this project, along with workflow executions that have been shared by any of its members.
title: Workflow Executions
show:
cancel_button: Cancel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

# Migration to add a new shared_with_namespace column to the workflow executions table
class AddSharedWithNamespaceToWorkflowExecutions < ActiveRecord::Migration[7.2]
def change
add_column :workflow_executions, :shared_with_namespace, :boolean, default: false, null: false
end
end
1 change: 1 addition & 0 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions test/components/nextflow_component_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ class NextflowComponentTest < ViewComponentTestCase
workflow:,
samples: [sample43, sample44],
url: 'https://nf-co.re/testpipeline',
namespace_id: 'SDSDDFDSFDS',
namespace_id: projects(:project1).namespace,
fields: %w[metadata_1 metadata_2 metadata_3]
)

assert_selector 'form' do
assert_selector 'h1', text: 'phac-nml/iridanextexample', count: 1
assert_selector 'input[type=text][name="workflow_execution[name]"]'
assert_selector 'input[type=checkbox][name="workflow_execution[shared_with_namespace]"]'
assert_text 'Share results with Project members?'
end
end

Expand Down Expand Up @@ -155,15 +157,17 @@ class NextflowComponentTest < ViewComponentTestCase
url: 'https://github.com/phac-nml/mikrokondo',
name: 'phac-nml/mikrokondo',
description: 'Mikrokondo pipeline'
}, '0.1.2',
},
{ 'name' => '0.1.2',
'automatable' => true },
Rails.root.join('test/fixtures/files/nextflow/mikrokondo/nextflow_schema.json'),
Rails.root.join('test/fixtures/files/nextflow/samplesheet_schema.json'))

render_inline NextflowComponent.new(
workflow:,
samples: [],
url: 'https://nf-co.re/testpipeline',
namespace_id: 'SDSDDFDSFDS',
namespace_id: projects(:project1).namespace,
fields: %w[metadata_1 metadata_2 metadata_3],
instance:
)
Expand All @@ -179,6 +183,7 @@ class NextflowComponentTest < ViewComponentTestCase
assert_selector 'input[type="radio"][name="workflow_execution[workflow_params][skip_depth_sampling]"][value="false"][checked="checked"]',
count: 1
assert_no_selector 'input[type="radio"][name="workflow_execution[workflow_params][skip_depth_sampling]"][value="true"][checked="checked"]'
assert_no_selector 'input[type=checkbox][name="workflow_execution[shared_with_namespace]"]'
end
# rubocop:enable Layout/LineLength
end
Expand Down
Loading

0 comments on commit 53d0e3e

Please sign in to comment.