diff --git a/apps/dashboard/app/models/project.rb b/apps/dashboard/app/models/project.rb index 96efe7dd06..9aaea27317 100644 --- a/apps/dashboard/app/models/project.rb +++ b/apps/dashboard/app/models/project.rb @@ -76,9 +76,6 @@ def templates validate :project_directory_exist, on: [:create] validate :project_template_invalid, on: [:create] - # the template you created this project from - attr_accessor :template - def initialize(attributes = {}) @id = attributes.delete(:id) @directory = attributes.delete(:directory) @@ -194,15 +191,39 @@ def sync_template return true if template.blank? # Sync the template files over - oe, s = Open3.capture2e("rsync", "-a", "#{template}/", "#{project_dataroot}") + oe, s = Open3.capture2e(*rsync_args) raise oe unless s.success? - true + save_new_scripts rescue StandardError => e errors.add(:save, "Failed to sync template: #{e.message}") false end + # When copying a project from a template, we need new Script objects + # that point to the _new_ project directory, not the template's directory. + # This creates them _and_ serializes them to yml in the new directory. + def save_new_scripts + dir = Script.scripts_dir(template) + Dir.glob("#{dir}/*/form.yml").map do |script_yml| + Script.from_yaml(script_yml, project_dataroot) + end.map do |script| + saved_successfully = script.save + errors.add(:save, script.errors.full_messages) unless saved_successfully + + saved_successfully + end.all? do |saved_successfully| + saved_successfully == true + end + end + + def rsync_args + [ + 'rsync', '-rltp', '--exclude', 'scripts/*', + "#{template}/", project_dataroot.to_s + ] + end + def project_directory_exist if !directory.blank? && Project.lookup_table.map { |_id, directory| directory }.map(&:to_s).include?(directory.to_s) errors.add(:directory, :used) diff --git a/apps/dashboard/app/models/script.rb b/apps/dashboard/app/models/script.rb index 15e678c295..298724616e 100644 --- a/apps/dashboard/app/models/script.rb +++ b/apps/dashboard/app/models/script.rb @@ -309,9 +309,8 @@ def add_required_fields(form: [], attributes: {}) end def add_script_to_form(form: [], attributes: {}) - return if form.include?('auto_scripts') + form << 'auto_scripts' unless form.include?('auto_scripts') - form << 'auto_scripts' attributes[:auto_scripts] = { directory: project_dir } diff --git a/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/manifest.yml b/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/manifest.yml new file mode 100644 index 0000000000..4e2246284a --- /dev/null +++ b/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/manifest.yml @@ -0,0 +1,4 @@ +--- +name: Chemistry 5533 +description: This is a template for a fake chemistry class - 5533. +icon: fas://atom diff --git a/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/scripts/8woi7ghd/form.yml b/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/scripts/8woi7ghd/form.yml new file mode 100644 index 0000000000..ff54090786 --- /dev/null +++ b/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/scripts/8woi7ghd/form.yml @@ -0,0 +1,54 @@ +--- +title: Assignment 1 +created_at: 1699454963 +form: +- auto_accounts +- auto_scripts +- auto_batch_clusters +attributes: + auto_accounts: + options: + - - pzs0715 + - pzs0715 + - {} + - - pzs1118 + - pzs1118 + - {} + - - pzs0714 + - pzs0714 + - {} + - - pzs1117 + - pzs1117 + - data-option-for-cluster-ascend: false + - - pas1604 + - pas1604 + - data-option-for-cluster-ascend: false + value: pzs0715 + fixed: true + label: Account + help: '' + required: false + auto_scripts: + options: + - - assignment_1.sh + - "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_1.sh" + - - assignment_2.sh + - "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_2.sh" + - - assignment_3.sh + - "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_3.sh" + directory: "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi" + value: "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_1.sh" + fixed: true + label: Script + help: '' + required: false + auto_batch_clusters: + options: + - ascend + - owens + - pitzer + value: owens + fixed: true + label: Cluster + help: '' + required: false diff --git a/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/scripts/gtqxzsek/form.yml b/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/scripts/gtqxzsek/form.yml new file mode 100644 index 0000000000..1f9421494b --- /dev/null +++ b/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/scripts/gtqxzsek/form.yml @@ -0,0 +1,57 @@ +--- +title: Assignment 2 +created_at: 1699455963 +form: +- auto_accounts +- auto_scripts +- auto_batch_clusters +attributes: + auto_accounts: + options: + - - pzs0715 + - pzs0715 + - {} + - - pzs1118 + - pzs1118 + - {} + - - pzs0714 + - pzs0714 + - {} + - - pzs1117 + - pzs1117 + - data-option-for-cluster-ascend: false + - - pas1604 + - pas1604 + - data-option-for-cluster-ascend: false + value: pzs0715 + fixed: true + label: Account + help: '' + required: false + auto_scripts: + options: + - - assignment_1.sh + - "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_1.sh" + - - assignment_2.sh + - "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_2.sh" + - - assignment_3.sh + - "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_3.sh" + directory: "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi" + value: "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_2.sh" + exclude_options: + - assignment_3.sh + - assignment_1.sh + fixed: true + label: Script + help: '' + required: false + auto_batch_clusters: + options: + - ascend + - owens + - pitzer + value: owens + fixed: true + label: Cluster + help: '' + required: false diff --git a/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/scripts/jxbrz2vm/form.yml b/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/scripts/jxbrz2vm/form.yml new file mode 100644 index 0000000000..9cd98e1378 --- /dev/null +++ b/apps/dashboard/test/fixtures/projects/chemistry-5533/.ondemand/scripts/jxbrz2vm/form.yml @@ -0,0 +1,57 @@ +--- +title: Assignment 3 +created_at: 1699456963 +form: +- auto_accounts +- auto_scripts +- auto_batch_clusters +attributes: + auto_accounts: + options: + - - pzs0715 + - pzs0715 + - {} + - - pzs1118 + - pzs1118 + - {} + - - pzs0714 + - pzs0714 + - {} + - - pzs1117 + - pzs1117 + - data-option-for-cluster-ascend: false + - - pas1604 + - pas1604 + - data-option-for-cluster-ascend: false + value: pzs0715 + fixed: true + label: Account + help: '' + required: false + auto_scripts: + options: + - - assignment_1.sh + - "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_1.sh" + - - assignment_2.sh + - "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_2.sh" + - - assignment_3.sh + - "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_3.sh" + directory: "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi" + value: "/users/PZS0714/johrstrom/ondemand/src/apps/dashboard/data/projects/cmtgfxfi/assignment_3.sh" + exclude_options: + - assignment_2.sh + - assignment_1.sh + fixed: true + label: Script + help: '' + required: false + auto_batch_clusters: + options: + - ascend + - owens + - pitzer + value: owens + fixed: true + label: Cluster + help: '' + required: false diff --git a/apps/dashboard/test/fixtures/projects/chemistry-5533/assignment_1.sh b/apps/dashboard/test/fixtures/projects/chemistry-5533/assignment_1.sh new file mode 100755 index 0000000000..49c32adcf7 --- /dev/null +++ b/apps/dashboard/test/fixtures/projects/chemistry-5533/assignment_1.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +#SBATCH --tasks-per-node 4 + + +INPUT_FILE=$PWD/input_data/assignment_1.inp +OUTPUT_DIR=$PWD/output_data + +module load intel +module load mvapich2 +module load qchem + +set -x + +# copy input file to $TMPDIR +cp $INPUT_FILE $TMPDIR +cd $TMPDIR +INPUT_FILE="$TMPDIR/assignment_1.inp" + +qchem -np $SLURM_NPROCS $INPUT_FILE +ls -lrta +cp assignment_1.fchk $OUTPUT_DIR \ No newline at end of file diff --git a/apps/dashboard/test/fixtures/projects/chemistry-5533/assignment_2.sh b/apps/dashboard/test/fixtures/projects/chemistry-5533/assignment_2.sh new file mode 100755 index 0000000000..a505bf3cd8 --- /dev/null +++ b/apps/dashboard/test/fixtures/projects/chemistry-5533/assignment_2.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +#SBATCH --tasks-per-node 4 + +INPUT_FILE=$PWD/input_data/assignment_1.inp +OUTPUT_DIR=$PWD/output_data + +module load intel +module load mvapich2 +module load qchem + +set -x + +# copy input file to $TMPDIR +cp $INPUT_FILE $TMPDIR/assignment_2.inp +cd $TMPDIR +INPUT_FILE="$TMPDIR/assignment_2.inp" + +qchem -np $SLURM_NPROCS $INPUT_FILE +ls -lrta +cp assignment_2.fchk $OUTPUT_DIR diff --git a/apps/dashboard/test/fixtures/projects/chemistry-5533/assignment_3.sh b/apps/dashboard/test/fixtures/projects/chemistry-5533/assignment_3.sh new file mode 100755 index 0000000000..f6dce5bc20 --- /dev/null +++ b/apps/dashboard/test/fixtures/projects/chemistry-5533/assignment_3.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +#SBATCH --tasks-per-node 4 + +INPUT_FILE=$PWD/input_data/assignment_1.inp +OUTPUT_DIR=$PWD/output_data + +module load intel +module load mvapich2 +module load qchem + +set -x + +# copy input file to $TMPDIR +cp $INPUT_FILE $TMPDIR/assignment_3.inp +cd $TMPDIR +INPUT_FILE="$TMPDIR/assignment_3.inp" + +qchem -np $SLURM_NPROCS $INPUT_FILE +ls -lrta +cp assignment_3.fchk $OUTPUT_DIR diff --git a/apps/dashboard/test/fixtures/projects/chemistry-5533/input_data/assignment_1.inp b/apps/dashboard/test/fixtures/projects/chemistry-5533/input_data/assignment_1.inp new file mode 100644 index 0000000000..7a7f2ba4a7 --- /dev/null +++ b/apps/dashboard/test/fixtures/projects/chemistry-5533/input_data/assignment_1.inp @@ -0,0 +1,34 @@ +$comment +ethene +$end + +$molecule +0 1 + C -2.3620772 0.9207308 0.2821606 + C -1.0130626 0.5278664 -0.2865418 + H -2.6220345 1.9481860 -0.0474867 + H -3.1392310 0.2143160 -0.0767561 + H -0.7529560 -0.4993875 0.0434971 + H -0.2358785 1.2345592 0.0718086 +$end + +$rem + BASIS = 6-31G* + EXCHANGE = HF + GUI = 2 + JOB_TYPE = Optimization + PURECART = 2222 +$end + +@@@ + +$molecule +read +$end + +$rem +basis 6-31G* +exchange hf +molden_format true +print_orbitals true +$end diff --git a/apps/dashboard/test/fixtures/projects/.keep b/apps/dashboard/test/fixtures/projects/chemistry-5533/output_data/.keep similarity index 100% rename from apps/dashboard/test/fixtures/projects/.keep rename to apps/dashboard/test/fixtures/projects/chemistry-5533/output_data/.keep diff --git a/apps/dashboard/test/fixtures/projects/projects/.project_lookup b/apps/dashboard/test/fixtures/projects/projects/.project_lookup deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/dashboard/test/system/jobs_app_test.rb b/apps/dashboard/test/system/jobs_app_test.rb index 88d2eba964..c69578fb81 100644 --- a/apps/dashboard/test/system/jobs_app_test.rb +++ b/apps/dashboard/test/system/jobs_app_test.rb @@ -716,4 +716,57 @@ def add_bc_num_hours(project_id, script_id) end end end + + test 'creating project from template updates forms' do + Dir.mktmpdir do |dir| + Project.stubs(:dataroot).returns(Pathname.new(dir)) + Configuration.stubs(:project_template_dir).returns("#{Rails.root}/test/fixtures/projects") + + visit(projects_root_path) + click_on(I18n.t('dashboard.jobs_create_template_project')) + + select('Chemistry 5533', from: 'project_template') + + # nothing in the users' project directory yet. + assert_equal(['.project_lookup'], Dir.children(dir)) + assert_equal('', File.read("#{dir}/.project_lookup")) + + click_on(I18n.t('dashboard.save')) + + assert_equal(2, Dir.children(dir).size) + project_dir = Dir.children(dir).select { |path| File.directory?("#{dir}/#{path}") }.first + abs_project_dir = "#{dir}/#{project_dir}" + + # 3 shell scripts, 3 forms were copied + assert_equal(3, Dir.glob("#{abs_project_dir}/*.sh").size) + forms = Dir.glob("#{abs_project_dir}/.ondemand/**/*/form.yml") + assert_equal(3, forms.size) + + orig_form = "#{Rails.root}/test/fixtures/projects/chemistry-5533/.ondemand/scripts/8woi7ghd/form.yml" + orig_form = YAML.safe_load(File.read(orig_form)) + + # ruby 2.7 globs differently + new_form = if RUBY_VERSION > '3.0' + YAML.safe_load(File.read(forms.first)) + else + YAML.safe_load(File.read(forms[2])) + end + + # 'form' & 'title' are the same + assert_equal(orig_form['form'], new_form['form']) + assert_equal(orig_form['title'], new_form['title']) + + # every auto_scripts option was copied and has the _new_ project location. + new_auto_scripts = new_form['attributes']['auto_scripts']['options'] + new_auto_scripts.sort_by do |option| + # ruby 2.7 doesn't sort the same way + option[0].match('\d').to_s.to_i + end.each_with_index do |option, idx| + filename = "assignment_#{idx + 1}.sh" + full_path = "#{abs_project_dir}/#{filename}" + assert_equal(filename, option[0]) + assert_equal(full_path, option[1]) + end + end + end end