Skip to content

Commit

Permalink
Merge pull request #8922 from rundeck/RUN-551
Browse files Browse the repository at this point in the history
RUN-551: Vue conversion of job option editor (WIP)
  • Loading branch information
gschueler committed Mar 18, 2024
2 parents 49a3978 + dac3c17 commit 972488f
Show file tree
Hide file tree
Showing 58 changed files with 5,953 additions and 472 deletions.
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
package com.dtolabs.rundeck.core.jobs.options;

import java.util.ArrayList;
import java.util.List;

import lombok.Getter;

import java.util.Map;
import java.util.TreeMap;

/**
* Defines the model for storing generic configuration data for a job option
*/
@Getter
public class JobOptionConfigData {
private final Map<String,JobOptionConfigEntry> jobOptionConfigEntries;
private final Map<String, JobOptionConfigEntry> jobOptionConfigEntries;

public JobOptionConfigData() {
this.jobOptionConfigEntries = new TreeMap<>();
}

public void addConfig(JobOptionConfigEntry jobOptionConfigEntry){
this.jobOptionConfigEntries.put(jobOptionConfigEntry.configType(),jobOptionConfigEntry);
public JobOptionConfigData(Map<String, JobOptionConfigEntry> values) {
this.jobOptionConfigEntries = new TreeMap<>(values);
}

public Map<String, JobOptionConfigEntry> getJobOptionConfigEntries() {
return jobOptionConfigEntries;
public void addConfig(JobOptionConfigEntry jobOptionConfigEntry) {
this.jobOptionConfigEntries.put(jobOptionConfigEntry.configType(), jobOptionConfigEntry);
}

public JobOptionConfigEntry getJobOptionEntry(Class classType){
return jobOptionConfigEntries.values().stream().filter(it->classType.isInstance(it)).findFirst().orElse(null);
public JobOptionConfigEntry getJobOptionEntry(Class<?> classType) {
return jobOptionConfigEntries.values().stream().filter(classType::isInstance).findFirst().orElse(null);
}

public JobOptionConfigEntry getJobOptionEntry(String configType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package org.rundeck.tests.functional.api.job

import org.rundeck.util.annotations.APITest
import org.rundeck.util.container.BaseContainer

@APITest
class JobOptionValidationSpec extends BaseContainer {

def setupSpec() {
startEnvironment()
setupProject()
}

def "validate job option fails for version<47"() {
given:
def client = getClient()
client.apiVersion = version
when:
def response = client.doPost(
"/project/${PROJECT_NAME}/jobs/validateOption",
[
"name" : "opt1",
"required": true
]
)
then:
verifyAll {
!response.successful
response.code() == 400
def json = client.jsonValue(response.body(), Map)
json.errorCode == 'api.error.api-version.unsupported'
}
where:
version << [14, 46]
}

def "validate job option ok for version>46"() {
given:
def client = getClient()
client.apiVersion = 47
when:
def response = client.doPost(
"/project/${PROJECT_NAME}/jobs/validateOption",
[
"name" : "opt1",
"required": true
]
)
then:
verifyAll {
response.successful
response.code() == 200
def json = client.jsonValue(response.body(), Map)
json.valid == true
}
}
def "validate job option invalid field for version>46"() {
given:
def client = getClient()
client.apiVersion = 47
when:
def response = client.doPost(
"/project/${PROJECT_NAME}/jobs/validateOption?jobWasScheduled=${sched}",
[
"name" : "opt1",
"required": true
]+extra
)
then:
verifyAll {
!response.successful
response.code() == 400
def json = client.jsonValue(response.body(), Map)
json.valid == false
json.messages!=null
json.messages[fieldName]!=null
json.messages[fieldName].size()>0
json.messages[fieldName].any{it.contains(message)}
}
where:
sched | extra | fieldName | message
false | [valuesUrl: 'notaurl'] | 'valuesUrl' | 'Not a valid URL'
false | [name: 'in valid'] | 'name' | 'does not match the required pattern'
false | [remoteUrlAuthenticationType: 'in valid'] | 'remoteUrlAuthenticationType' | 'must be in the list'
false | [storagePath: 'in valid'] | 'storagePath' | 'Default key storage path must start with: keys/: in valid'
false | [hidden: true] | 'value' | 'Hidden options must have a default value or storage path.'
false | [hidden: true, secure:true] | 'storagePath' | 'Hidden options must have a default value or storage path.'
false | [valuesType: 'url', enforced:true] | 'valuesUrl' | 'Allowed values (list or remote URL) must be specified if values are enforced'
false | [valuesType: 'list', enforced:true] | 'values' | 'Allowed values (list or remote URL) must be specified if values are enforced'
false | [value: 'b',values:['a'], enforced:true] | 'value' | 'Default Value was not in the allowed values list, and values are enforced'
false | [multivalued:true,delimiter:null] | 'delimiter' | 'You must specify a delimiter for multivalued options'
false | [value: 'a,b',values:['a'], enforced:true,multivalued:true,delimiter:','] | 'value' | 'Default Value contains a string that was not in the allowed values list, and values are enforced'
false | [regex:'asdf['] | 'regex' | 'Invalid Regular Expression:'
false | [regex:'[a-f]+',value:'z'] | 'value' | 'Default value "z" does not match the regex: [a-f]+'
false | [regex:'[a-f]+',values:['z']] | 'values' | 'Allowed value "z" does not match the regex: [a-f]+'
false | [multivalued: true, secure:true] | 'multivalued' | 'Secure input cannot be used with multi-valued input'
true | [required: true, optionType:'file'] | 'required' | 'File option type cannot be Required when the Job is scheduled'
true | [required: true] | 'value' | 'Specify a Default Value for Required options when the Job is scheduled'
true | [valuesUrl:'http://x.com',configRemoteUrl:[jsonFilter:'bad[.']] | 'configRemoteUrl.jsonFilter' | 'The Remote URL Json Path Filter has an invalid syntax'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,25 @@ class BasicJobsSpec extends SeleniumBase {

def "create valid job basic options"() {
when:
def jobCreatePage = go JobCreatePage, SELENIUM_BASIC_PROJECT
def jobCreatePage = page JobCreatePage, SELENIUM_BASIC_PROJECT
jobCreatePage.nextUi=nextUi
jobCreatePage.go()
def jobShowPage = page JobShowPage
def optionName = 'seleniumOption1'
then:
jobCreatePage.fillBasicJob 'a job with options'
jobCreatePage.fillBasicJob specificationContext.currentIteration.name+" ${nextUi ? "next ui" : "old ui"}"
jobCreatePage.optionButton.click()
jobCreatePage.optionName 0 sendKeys optionName
jobCreatePage.optionNameNew() sendKeys optionName
jobCreatePage.executeScript "arguments[0].scrollIntoView(true);", jobCreatePage.saveOptionButton
jobCreatePage.saveOptionButton.click()
jobCreatePage.waitFotOptLi 0
jobCreatePage.createJobButton.click()
then:
jobCreatePage.waitForUrlToContain('/job/show')
jobShowPage.jobLinkTitleLabel.getText().contains('a job with options')
jobShowPage.jobLinkTitleLabel.getText().contains('create valid job basic options')
jobShowPage.optionInputText(optionName) != null
where:
nextUi<<[false,true]
}

def "edit job set description"() {
Expand Down Expand Up @@ -201,7 +205,7 @@ class BasicJobsSpec extends SeleniumBase {
jobShowPage.optionValidationWarningText.getText().contains 'Option \'reqOpt1\' is required'
}

def "job filter by name 3 results"() {
def "job filter by name results"() {
when:
def jobShowPage = go JobShowPage, SELENIUM_BASIC_PROJECT
then:
Expand All @@ -210,11 +214,18 @@ class BasicJobsSpec extends SeleniumBase {
jobShowPage.waitForModal 1
jobShowPage.jobSearchNameField.sendKeys 'option'
jobShowPage.jobSearchSubmitButton.click()
jobShowPage.waitForNumberOfElementsToBe jobShowPage.jobRowBy, 3
jobShowPage.jobRowLink.size() == 3
jobShowPage.waitForNumberOfElementsToBe jobShowPage.jobRowBy, expected.size()
jobShowPage.jobRowLink.size() == expected.size()
jobShowPage.jobRowLink.collect {
it.getText()
}.containsAll(["selenium-option-test1", "predefined job with options", "a job with options"])
}.containsAll(expected)
where:
expected = [
"selenium-option-test1",
"predefined job with options",
"create valid job basic options next ui",
"create valid job basic options old ui"
]
}

def "job filter by name and group 1 results"() {
Expand All @@ -233,18 +244,26 @@ class BasicJobsSpec extends SeleniumBase {
}

def "job filter by name and - top group 2 results"() {
when:
given:
def jobShowPage = go JobShowPage, SELENIUM_BASIC_PROJECT
then:
when:
jobShowPage.validatePage()
jobShowPage.jobSearchButton.click()
jobShowPage.waitForModal 1
jobShowPage.jobSearchNameField.sendKeys 'option'
jobShowPage.jobSearchGroupField.sendKeys '-'
jobShowPage.jobSearchSubmitButton.click()
expect:
jobShowPage.waitForNumberOfElementsToBe jobShowPage.jobRowBy, 2
jobShowPage.jobRowLink.collect { it.getText() } == ["a job with options", "predefined job with options"]
then:
jobShowPage.waitForNumberOfElementsToBe jobShowPage.jobRowBy, expected.size()
def names = jobShowPage.jobRowLink.collect { it.getText() }
names.size() == expected.size()
names.containsAll expected
where:
expected = [
"predefined job with options",
"create valid job basic options next ui",
"create valid job basic options old ui"
]
}

def "view jobs list page"() {
Expand Down

0 comments on commit 972488f

Please sign in to comment.