Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new option features ( sort, list delimiter). #4599

Merged
merged 5 commits into from May 28, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -509,19 +509,19 @@ class EditOptsController {
opt.errors.rejectValue('hidden', 'option.hidden.notallowed.message')
return result
}
if (opt.enforced && (opt.values || opt.valuesList) && opt.defaultValue) {
if (opt.enforced && (opt.optionValues || opt.valuesList) && opt.defaultValue) {
opt.convertValuesList()
if(!opt.multivalued && !opt.values.contains(opt.defaultValue)) {
if(!opt.multivalued && !opt.optionValues.contains(opt.defaultValue)) {
opt.errors.rejectValue('defaultValue', 'option.defaultValue.notallowed.message')
}else if(opt.multivalued && opt.delimiter){
//validate each default value
def found = opt.defaultValue.split(Pattern.quote(opt.delimiter)).find{ !opt.values.contains(it) }
def found = opt.defaultValue.split(Pattern.quote(opt.delimiter)).find{ !opt.optionValues.contains(it) }
if(found){
opt.errors.rejectValue('defaultValue', 'option.defaultValue.multivalued.notallowed.message',[found] as Object[],"{0} invalid value")
}
}
}
if (opt.enforced && (!opt.values && !opt.valuesList && !opt.realValuesUrl && !opt.optionValuesPluginType)) {
if (opt.enforced && (!opt.optionValues && !opt.valuesList && !opt.realValuesUrl && !opt.optionValuesPluginType)) {
if (params && params.valuesType == 'url') {
opt.errors.rejectValue('valuesUrl', 'option.enforced.emptyvalues.message')
} else {
Expand All @@ -536,10 +536,10 @@ class EditOptsController {
result.regexError = e.message
opt.errors.rejectValue('regex', 'option.regex.invalid.message', [opt.regex] as Object[], "Invalid Regex: {0}")
}
if (opt.values || opt.valuesList) {
if (opt.optionValues || opt.valuesList) {
opt.convertValuesList()
def inval = []
opt.values.each {val ->
opt.optionValues.each {val ->
if (!(val =~ /${opt.regex}/)) {
opt.errors.rejectValue('values', 'option.values.regexmismatch.message', [val.toString(), opt.regex] as Object[], "Value does not match regex: {0}")
}
Expand Down Expand Up @@ -578,6 +578,8 @@ class EditOptsController {
}
if(!hasSelectedOnRemoteValue) opt.errors.rejectValue('defaultValue', 'option.defaultValue.required.message')
}
//we will not persist field values anymore (replace it for option.valueList)
opt.values = null
return result
}

Expand Down Expand Up @@ -638,12 +640,19 @@ class EditOptsController {
params.secureExposed = false
params.isDate = true
}

if(null==params.sortValues){
params.sortValues=false
}
opt.properties = params
if (params.optionType && params.configMap) {
opt.configMap = params.configMap
}
opt.valuesList = params.valuesList
if(params.valuesList){
opt.valuesList = params.valuesList
}else{
opt.valuesList = null
opt.values = null
}
if(params.valuesType == 'list'){
opt.realValuesUrl=null
}else if(params.valuesType == 'url'){
Expand Down
72 changes: 63 additions & 9 deletions rundeckapp/grails-app/domain/rundeck/Option.groovy
Expand Up @@ -16,7 +16,9 @@

package rundeck

import com.dtolabs.rundeck.app.api.marshall.CollectionElement
import com.dtolabs.rundeck.plugins.option.OptionValue
import com.dtolabs.rundeck.util.StringNumericSort
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.databind.ObjectMapper

Expand All @@ -41,6 +43,7 @@ import java.util.regex.Pattern

public class Option implements Comparable{

static final String DEFAULT_DELIMITER =','

ScheduledExecution scheduledExecution
String name
Expand All @@ -62,6 +65,7 @@ public class Option implements Comparable{
URL valuesUrlLong
String regex
String valuesList
String valuesListDelimiter
Boolean multivalued
String delimiter
Boolean secureInput
Expand All @@ -72,9 +76,12 @@ public class Option implements Comparable{
String optionValuesPluginType
List<OptionValue> valuesFromPlugin
Boolean hidden
Boolean sortValues
List<String> optionValues


static belongsTo=[scheduledExecution:ScheduledExecution]
static transients = ['valuesList', 'realValuesUrl', 'configMap', 'typeFile','valuesFromPlugin']
static transients = ['realValuesUrl', 'configMap', 'typeFile','valuesFromPlugin','optionValues']

static constraints={
name(nullable:false,blank:false,matches: '[a-zA-Z_0-9.-]+')
Expand All @@ -101,6 +108,9 @@ public class Option implements Comparable{
label(nullable: true)
optionValuesPluginType(nullable: true)
hidden(nullable: true)
valuesList(nullable: true)
valuesListDelimiter(nullable: true)
sortValues(nullable: true)
}


Expand Down Expand Up @@ -136,6 +146,7 @@ public class Option implements Comparable{
regex type: 'text'
optionType type: 'text'
configData type: 'text'
valuesList type: 'text'
// values type: 'string'//, lazy: false
}
/**
Expand Down Expand Up @@ -181,8 +192,9 @@ public class Option implements Comparable{
if(regex){
map.regex=regex
}
if(values){
map.values=values as List
if(getOptionValues()){
map.values=getOptionValues()
map.valuesListDelimiter = valuesListDelimiter?:','
}
if(multivalued){
map.multivalued=multivalued
Expand Down Expand Up @@ -245,6 +257,13 @@ public class Option implements Comparable{
}
if(data.values){
opt.values=data.values instanceof Collection?new TreeSet(data.values):new TreeSet([data.values])
if(data.valuesListDelimiter){
opt.valuesListDelimiter=data.valuesListDelimiter
}else{
opt.valuesListDelimiter=DEFAULT_DELIMITER
}
opt.valuesList = opt.produceValuesList()
opt.values = null
}
if(data.multivalued){
opt.multivalued=true
Expand Down Expand Up @@ -276,7 +295,12 @@ public class Option implements Comparable{
*/
public String produceValuesList(){
if(values){
return values.join(",")
if(valuesListDelimiter==null){
valuesListDelimiter = DEFAULT_DELIMITER
}
valuesList = values.join(valuesListDelimiter)
values = null
return valuesList
}else{
return ''
}
Expand Down Expand Up @@ -311,10 +335,15 @@ public class Option implements Comparable{
*/
void convertValuesList(){
if(valuesList!=null){
def x=new TreeSet()
x.addAll(valuesList.split(",").collect{it.trim()}.grep{it} as List)
values=x
valuesList=null
if(valuesListDelimiter == null){
valuesListDelimiter=DEFAULT_DELIMITER
}
optionValues=new ArrayList()
optionValues.addAll(valuesList.split(Pattern.quote(valuesListDelimiter)).collect{it.trim()}.grep{it}.unique() as List)

if(optionValues && sortValues){
sortValuesList()
}
}
}

Expand Down Expand Up @@ -357,7 +386,7 @@ public class Option implements Comparable{
'dateFormat', 'values', 'valuesList', 'valuesUrl', 'valuesUrlLong', 'regex', 'multivalued',
'multivalueAllSelected', 'label',
'delimiter', 'optionValuesPluginType',
'secureInput', 'secureExposed', 'optionType', 'configData', 'hidden'].
'secureInput', 'secureExposed', 'optionType', 'configData', 'hidden','sortValues','valuesListDelimiter'].
each { k ->
opt[k]=this[k]
}
Expand All @@ -367,6 +396,31 @@ public class Option implements Comparable{
return opt
}


void sortValuesList(){
def numericValues = optionValues.findAll { it.isNumber() }.collect {it.toDouble()}
if(numericValues.size()==optionValues.size()){
def stringNumericList= optionValues.findAll { it.isNumber() }.collect {new StringNumericSort(it, it.toDouble())}
StringNumericSort.sortNumeric(stringNumericList)
optionValues = stringNumericList.collect{it.strValue}
}else{
optionValues = optionValues.sort()
}
}

List<String> getOptionValues(){
if(optionValues==null){
if(values !=null && values.size()> 0 && valuesList == null){
produceValuesList()
convertValuesList()
values = null
}else if(valuesList){
convertValuesList()
}
}
optionValues
}

public String toString ( ) {
return "Option{" +
"name='" + name + '\'' +
Expand Down
6 changes: 6 additions & 0 deletions rundeckapp/grails-app/i18n/messages.properties
Expand Up @@ -831,6 +831,12 @@ form.option.valuesType.url.label=Remote URL
form.option.valuesUrl.description=A URL to a Remote JSON service.
rundeck.user.guide.option.model.provider=Rundeck User Guide - Option model provider
form.option.enforcedType.label=Restrictions
form.option.sort.label=Sort Values
form.option.sort.description=Sort list Allowed Values
form.option.valuesDelimiter.label=List Delimiter
form.option.valuesDelimiter.description=Set the delimiter for Allowed Values
form.option.valuesList.placeholder=Delimiter separated list (comma by default)
form.option.valuesURL.placeholder=Remote URL
none=None
form.option.enforcedType.none.label=Any values can be used
form.option.enforced.label=Enforced from Allowed Values
Expand Down
6 changes: 6 additions & 0 deletions rundeckapp/grails-app/i18n/messages_es_419.properties
Expand Up @@ -820,6 +820,12 @@ form.option.create.title=Guardar la nueva opción
discard=Descartar
form.option.discard.title=Descartar los cambios a la opción
form.option.save.title=Guardar los cambios a la opción
form.option.sort.label=Ordernar valores
form.option.sort.description=Ordenar la lista de valores
form.option.valuesDelimiter.label=Delimitador de la lista
form.option.valuesDelimiter.description=Definir el delimitador de la lista de valores permitidos
form.option.valuesList.placeholder=Lista separada por delimitador (por defecto es coma)
form.option.valuesURL.placeholder=URL Remota
ScheduledExecution.property.description.description=La primera línea de la descripción se mostrará en texto sin formato, el resto se representará con úna Marca.\n\n\
See [Markdown](http://en.wikipedia.org/wiki/Markdown).\n\n\
Within the extended description you can link to the job using `{{job.permalink}}` as the URL to the job, e.g. `[run job]({{job.permalink}}#runjob)`\n\n\
Expand Down
Expand Up @@ -2600,14 +2600,14 @@ class ExecutionService implements ApplicationContextAware, StepExecutor, NodeSte
return
}
}
if (opt.enforced && opt.values && optparams[opt.name]) {
if (opt.enforced && opt.optionValues && optparams[opt.name]) {
def val
if (optparams[opt.name] instanceof Collection) {
val = [optparams[opt.name]].flatten();
} else {
val = optparams[opt.name].toString().split(Pattern.quote(opt.delimiter))
}
if (!opt.values.containsAll(val.grep { it })) {
if (!opt.optionValues.containsAll(val.grep { it })) {
invalidOpt opt,lookupMessage("domain.Option.validation.allowed.values",[opt.name,optparams[opt.name],opt.values])
return
}
Expand All @@ -2622,10 +2622,10 @@ class ExecutionService implements ApplicationContextAware, StepExecutor, NodeSte
return
}
}
if (opt.enforced && opt.values &&
if (opt.enforced && opt.optionValues &&
optparams[opt.name] &&
optparams[opt.name] instanceof String &&
!opt.values.contains(optparams[opt.name])) {
!opt.optionValues.contains(optparams[opt.name])) {
invalidOpt opt, opt.secureInput ?
lookupMessage("domain.Option.validation.secure.invalid",[opt.name])
: lookupMessage("domain.Option.validation.allowed.invalid",[opt.name,optparams[opt.name],opt.values])
Expand Down
Expand Up @@ -523,6 +523,9 @@ class JobsXMLCodec {
if(x.values){
BuilderUtil.addAttribute(x,'values',x.remove('values').join(","))
}
if(x.valuesListDelimiter){
BuilderUtil.addAttribute(x,'valuesListDelimiter',x.remove('valuesListDelimiter'))
}
if(x.enforced){
//convert 'enforced' to @enforcedvalues
BuilderUtil.addAttribute(x,'enforcedvalues',x.remove('enforced'))
Expand Down
4 changes: 2 additions & 2 deletions rundeckapp/grails-app/views/framework/_jobOptionsKO.gsp
Expand Up @@ -61,7 +61,7 @@ used by _editOptions.gsp template
dateFormat : optionSelect.dateFormat,
optionType : optionSelect.optionType,
// config : optionSelect.configMap,
values : optionSelect.optionValuesPluginType ? optionSelect.valuesFromPlugin.collect { it.value } : optionSelect.values,
values : optionSelect.optionValuesPluginType ? optionSelect.valuesFromPlugin.collect { it.value } : optionSelect.optionValues,
defaultValue : optionSelect.defaultValue,
defaultStoragePath : optionSelect.defaultStoragePath,
multivalued : optionSelect.multivalued,
Expand All @@ -77,7 +77,7 @@ used by _editOptions.gsp template
optionDepsMet : !optiondependencies[optName] || selectedoptsmap &&
optiondependencies[optName].every { selectedoptsmap[it] },
secureInput : optionSelect.secureInput,
hasExtended : !optionSelect.secureInput && (values || optionSelect.values ||
hasExtended : !optionSelect.secureInput && (values || optionSelect.optionValues ||
optionSelect.multivalued || optionSelect.valuesFromPlugin),
value : selectedvalue ? selectedvalue :
selectedoptsmap && null != selectedoptsmap[optName] ?
Expand Down
43 changes: 41 additions & 2 deletions rundeckapp/grails-app/views/scheduledExecution/_optEdit.gsp
Expand Up @@ -415,7 +415,7 @@
class="form-control"
value="${listvalue ? listvalue : listjoin ? listjoin.join(',') : ''}"
size="60"
placeholder="Comma separated list"
placeholder="${message(code:"form.option.valuesList.placeholder")}"
id="vlist_${rkey}"
/>

Expand All @@ -428,7 +428,7 @@
name="valuesUrl"
value="${option?.realValuesUrl?.toString()}"
size="60"
placeholder="Remote URL"
placeholder="${message(code:"form.option.valuesURL.placeholder")}"
id="vurl_${rkey}"
/>

Expand Down Expand Up @@ -466,6 +466,45 @@
</wdgt:eventHandler>
</div>
</div>
<div class="form-group">

<label class="col-sm-2 control-label"><g:message code="form.option.sort.label" /></label>

<div class="col-sm-3">
<div class="radio radio-inline">
<g:radio id="option-sort-values-no" name="sortValues" value="false" checked="${!option || !option.sortValues}"/>
<label for="option-sort-values-no">
<g:message code="no" />
</label>
</div>
<div class="radio radio-inline">
<g:radio id="option-sort-values-yes" name="sortValues" value="true" checked="${option?.sortValues}"/>
<label for="option-sort-values-yes">
<g:message code="yes" />
</label>
</div>
<div class="help-block">
<g:message code="form.option.sort.description"/>
</div>
</div>

<div class="input-group col-sm-3 ${hasErrors(bean: option, field: 'delimiter', 'has-error')}">
<div class="input-group-addon">
<g:message code="form.option.valuesDelimiter.label" />
</div>
<input type="text"
name="valuesListDelimiter"
value="${enc(attr:option?.valuesListDelimiter)}"
size="5"
class="form-control"
id="vlistdelimiter_${enc(attr:rkey)}"
/>

</div>
<span class="help-block">
<g:message code="form.option.valuesDelimiter.description"/>
</span>
</div>
<div class="form-group opt_keystorage_disabled" style="${wdgt.styleVisible(unless:option?.defaultStoragePath)}">
<label class="col-sm-2 control-label"><g:message code="form.option.enforcedType.label" /></label>
<div class="col-sm-10">
Expand Down
Expand Up @@ -37,7 +37,7 @@
<span class="desc"><g:strip>${option.description}</g:strip></span>
</span>
<g:if test="${option?.values || option.valuesList}">
<g:set var="opts" value="${option.values?option.values.sort():option.valuesList.split(',').sort()}"/>
<g:set var="opts" value="${option.optionValues}"/>
<div class="popout detailpopup" id="vls_${rkey}_tooltip" style="width:200px;display:none;" >
<div class="info note">Allowed Values</div>
<g:each var="val" in="${opts}" status="i"><g:enc>${0!=i?', ':''}</g:enc><span class="valueItem"><g:enc>${val}</g:enc></span></g:each>
Expand Down