Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/dashboard/app/assets/stylesheets/workflows.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ html, body {

.toolbar {
display: flex;
flex-wrap: wrap;
gap: .5rem;
align-items: center;
padding: .5rem .75rem;
Expand All @@ -48,6 +49,14 @@ html, body {
z-index: 5;
}

.toolbar .workflow-actions {
display: flex;
flex-basis: 100%;
gap: .5rem;
margin-top: .5rem;
justify-content: flex-start;
}

.toolbar button {
border: 1.5px solid #d5d8e0;
background: #fff;
Expand Down
144 changes: 96 additions & 48 deletions apps/dashboard/app/controllers/workflows_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

# The controller for apps pages /dashboard/projects/:project_id/workflows/:workflow_id
class WorkflowsController < ApplicationController

wrap_parameters false

# GET /projects/:id/workflows/:id
def show
@project = Project.find(project_id)
@workflow = Workflow.find(workflow_id, project_directory)
return unless load_project_and_workflow_objects
launcher_ids = @workflow.launcher_ids

@launchers = Launcher.all(project_directory).select { |l| launcher_ids.include?(l.id) }
Expand All @@ -20,33 +20,19 @@ def new

# GET /projects/:id/workflows/edit
def edit
@workflow = Workflow.find(workflow_id, project_directory)
return unless load_project_and_workflow_objects
@launchers = Launcher.all(project_directory)

return unless @workflow.nil?
redirect_to project_path(project_id), notice: I18n.t('dashboard.jobs_workflow_not_found', workflow_id: workflow_id)
end

# TODO to remove this with launcher_ids as we will need them after new UI
# PATCH /projects/:id/workflows/patch
def update
@workflow = Workflow.find(workflow_id, project_directory)
return unless load_project_and_workflow_objects

if @workflow.nil?
redirect_to project_path(project_id), notice: I18n.t('dashboard.jobs_workflow_not_found', workflow_id: workflow_id)
return
end

if @workflow.update(update_params)
if @workflow.update(update_params, true)
redirect_to project_path(project_id), notice: I18n.t('dashboard.jobs_workflow_manifest_updated')
else
# TODO: Rename "jobs_project_*"" to "jobs_*" to generalize
message = if @workflow.errors[:save].empty?
I18n.t('dashboard.jobs_project_validation_error')
else
I18n.t('dashboard.jobs_project_generic_error', error: @workflow.collect_errors)
end
flash.now[:alert] = message
render :edit
handle_workflow_error(:update)
end
end

Expand All @@ -57,25 +43,13 @@ def create
if @workflow.save
redirect_to project_path(project_id), notice: I18n.t('dashboard.jobs_workflow_created')
else
# TODO: Rename "jobs_project_*"" to "jobs_*" to generalize
message = if @workflow.errors[:save].empty?
I18n.t('dashboard.jobs_project_validation_error')
else
I18n.t('dashboard.jobs_project_generic_error', error: @workflow.collect_errors)
end
flash.now[:alert] = message
render :new
handle_workflow_error(:create)
end
end

# DELETE /projects/:id/workflows/:id
def destroy
@workflow = Workflow.find(workflow_id, project_directory)

if @workflow.nil?
redirect_to project_path(project_id), notice: I18n.t('dashboard.jobs_workflow_not_found', workflow_id: workflow_id)
return
end
return unless load_project_and_workflow_objects

if @workflow.destroy!
redirect_to project_path(project_id), notice: I18n.t('dashboard.jobs_workflow_deleted')
Expand All @@ -84,22 +58,65 @@ def destroy
end
end

# POST /projects/:project_id/workflows/:id/save
def save
return unless load_project_and_workflow_objects(render_json: true)
status = @workflow.update(metadata_params(permit_json_data))
if status
render json: { message: "Workflow saved successfully" }
else
render json: { message: @workflow.collect_errors }, status: :unprocessable_entity
end
end

# GET /projects/:project_id/workflows/:id/load
def load
return unless load_project_and_workflow_objects(render_json: true)
manifest_path = @workflow.manifest_file
unless File.exist?(manifest_path)
return render json: { message: 'No saved workflow manifest found.' }, status: :not_found
end

data = YAML.load_file(manifest_path)
metadata = data['metadata'] || {}
render json: {
boxes: metadata['boxes'] || [],
edges: metadata['edges'] || [],
zoom: metadata['zoom'] || 1.0,
saved_at: metadata['saved_at'] || nil
}
end


# POST /projects/:project_id/workflows/:id/submit
def submit
@project = Project.find(project_id)
@workflow = Workflow.find(workflow_id, project_directory)

result = @workflow.submit(submit_params)
return unless load_project_and_workflow_objects(render_json: true)
metadata = metadata_params(permit_json_data)
@workflow.update(metadata)
result = @workflow.submit(submit_params(metadata))
if result
redirect_to project_workflow_path(@project.id, @workflow.id), notice: I18n.t('dashboard.jobs_workflow_submitted')
render json: { message: "Workflow submitted successfully" }
else
message = I18n.t('dashboard.jobs_workflow_failed', error: @workflow.collect_errors)
redirect_to project_workflow_path(@project.id, @workflow.id), alert: message
msg = I18n.t('dashboard.jobs_workflow_failed', error: @workflow.collect_errors)
render json: { message: msg }, status: :unprocessable_entity
end
end

private

def load_project_and_workflow_objects(render_json: false)
@project = Project.find(project_id)
@workflow = Workflow.find(workflow_id, project_directory)
return true if @workflow.present?

if render_json
render json: { message: I18n.t('dashboard.jobs_workflow_not_found', workflow_id: workflow_id) }, status: :not_found
else
redirect_to project_path(project_id), notice: I18n.t('dashboard.jobs_workflow_not_found', workflow_id: workflow_id)
end
false
end

def index_params
params.permit(:project_id).to_h.symbolize_keys
end
Expand All @@ -125,15 +142,46 @@ def update_params
.permit(:name, :description, :id, launcher_ids: [])
end

def submit_params
params
.require(:workflow)
.permit(:name, :description, :id, launcher_ids: [], source_ids: [], target_ids: [])
.merge(project_dir: project_directory)
def submit_params(metadata)
meta = metadata[:metadata] || {}
{
launcher_ids: meta[:boxes].map { |b| b["id"] },
source_ids: meta[:edges].map { |e| e["from"] },
target_ids: meta[:edges].map { |e| e["to"] },
project_dir: project_directory
}
end

def project_directory
Project.find(params[:project_id]).directory
end

def permit_json_data
params.permit(:project_id, :id, :zoom, :saved_at, boxes: [:id, :title, :row, :col], edges: [:from, :to]).to_h
end

def metadata_params(json)
{
metadata:
{
boxes: json["boxes"] || [],
edges: json["edges"] || [],
zoom: json["zoom"] || 1.0,
saved_at: json["saved_at"] || Time.now.to_i
}
}
end

def handle_workflow_error(operation)
# TODO: Rename "jobs_project_*"" to "jobs_*" to generalize
message =
if @workflow.errors[:save].empty?
I18n.t('dashboard.jobs_project_validation_error')
else
I18n.t('dashboard.jobs_project_generic_error', error: @workflow.collect_errors)
end

flash.now[:alert] = message
render operation == :create ? :new : :edit
end
end
5 changes: 5 additions & 0 deletions apps/dashboard/app/javascript/dag.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export class DAG {
this.#adjacency_list = {};
}

reset() {
this.#launcher_list = new Set();
this.#adjacency_list = {};
}

addEdge(fromId, toId) {
if (!this.#launcher_list.has(fromId)) {
this.#launcher_list.add(fromId);
Expand Down
Loading