Skip to content

Commit

Permalink
feat: display value selector
Browse files Browse the repository at this point in the history
* #81 add a displayExpression property, that allows a different display value to the one included in env variables

* #81 fix test configuration

* #81 enhancements based on review comments

* #81 revert select option values to be the actual value

* fix: parameter rebuild behaviour

* refactor display value logic

* bump version

* remove no longer working tooltip

Co-authored-by: László Stahorszki <[email protected]>
Co-authored-by: h1dden-da3m0n <[email protected]>
  • Loading branch information
3 people authored Oct 21, 2021
1 parent f104328 commit 64dac20
Show file tree
Hide file tree
Showing 16 changed files with 303 additions and 109 deletions.
2 changes: 1 addition & 1 deletion .idea/codeStyles/Project.xml

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

2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>

<properties>
<revision>1.4.3</revision>
<revision>1.5.0</revision>
<changelist>-SNAPSHOT</changelist>
<!-- Baseline Jenkins version you use to build the plugin. Users must have this version or newer to run. -->
<jenkins.version>2.263.4</jenkins.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import hudson.Extension;
import hudson.model.Item;
import io.jenkins.plugins.restlistparam.logic.ValueResolver;
import io.jenkins.plugins.restlistparam.model.ValueItem;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterValue;
import hudson.model.SimpleParameterDefinition;
Expand All @@ -21,7 +23,6 @@
import org.kohsuke.stapler.*;
import org.kohsuke.stapler.verb.POST;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
Expand All @@ -36,23 +37,25 @@ public final class RestListParameterDefinition extends SimpleParameterDefinition
private final String credentialId;
private final MimeType mimeType;
private final String valueExpression;
private String displayExpression;
private ValueOrder valueOrder;
private String defaultValue;
private String filter;
private Integer cacheTime;
private String errorMsg;
private List<String> values;
private List<ValueItem> values;

@DataBoundConstructor
public RestListParameterDefinition(final String name,
final String description,
final String restEndpoint,
final String credentialId,
final MimeType mimeType,
final String valueExpression)
final String valueExpression,
final String displayExpression)
{
this(name, description, restEndpoint, credentialId, mimeType, valueExpression,
ValueOrder.NONE, ".*", config.getCacheTime(), "");
displayExpression, ValueOrder.NONE, ".*", config.getCacheTime(), "");
}

public RestListParameterDefinition(final String name,
Expand All @@ -61,6 +64,7 @@ public RestListParameterDefinition(final String name,
final String credentialId,
final MimeType mimeType,
final String valueExpression,
final String displayExpression,
final ValueOrder valueOrder,
final String filter,
final Integer cacheTime,
Expand All @@ -71,6 +75,9 @@ public RestListParameterDefinition(final String name,
this.mimeType = mimeType;
this.valueExpression = valueExpression;
this.credentialId = StringUtils.isNotBlank(credentialId) ? credentialId : "";
if (mimeType == MimeType.APPLICATION_JSON) {
this.displayExpression = StringUtils.isNotBlank(displayExpression) ? displayExpression : "$";
}
this.defaultValue = StringUtils.isNotBlank(defaultValue) ? defaultValue : "";
this.valueOrder = valueOrder != null ? valueOrder : ValueOrder.NONE;
this.filter = StringUtils.isNotBlank(filter) ? filter : ".*";
Expand Down Expand Up @@ -99,6 +106,18 @@ public String getFilter() {
return filter;
}

public String getDisplayExpression() {
if (mimeType == MimeType.APPLICATION_JSON) {
return StringUtils.isNotBlank(displayExpression) ? displayExpression : "$";
}
return "";
}

@DataBoundSetter
public void setDisplayExpression(final String displayExpression) {
this.displayExpression = displayExpression;
}

@DataBoundSetter
public void setValueOrder(final ValueOrder valueOrder) {
this.valueOrder = valueOrder;
Expand Down Expand Up @@ -139,15 +158,16 @@ public String getErrorMsg() {
return errorMsg;
}

public List<String> getValues() {
public List<ValueItem> getValues() {
Optional<StandardCredentials> credentials = CredentialsUtils.findCredentials(null, credentialId);

ResultContainer<List<String>> container = RestValueService.get(
ResultContainer<List<ValueItem>> container = RestValueService.get(
getRestEndpoint(),
credentials.orElse(null),
getMimeType(),
getCacheTime(),
getValueExpression(),
getDisplayExpression(),
getFilter(),
getValueOrder());

Expand All @@ -162,7 +182,8 @@ public ParameterDefinition copyWithDefaultValue(final ParameterValue defaultValu
RestListParameterValue value = (RestListParameterValue) defaultValue;
return new RestListParameterDefinition(
getName(), getDescription(), getRestEndpoint(), getCredentialId(), getMimeType(),
getValueExpression(), getValueOrder(), getFilter(), getCacheTime(), value.getValue());
getValueExpression(), getDisplayExpression(), getValueOrder(), getFilter(), getCacheTime(),
ValueResolver.parseDisplayValue(getMimeType(), value.getValue(), displayExpression));
}
else {
return this;
Expand All @@ -172,16 +193,17 @@ public ParameterDefinition copyWithDefaultValue(final ParameterValue defaultValu
@Override
public ParameterValue createValue(final String value) {
RestListParameterValue parameterValue = new RestListParameterValue(getName(), value, getDescription());

checkValue(parameterValue);
return parameterValue;
}

@Override
@CheckForNull
public ParameterValue createValue(final StaplerRequest req,
final JSONObject jo)
{
RestListParameterValue value = req.bindJSON(RestListParameterValue.class, jo);

checkValue(value);
return value;
}
Expand All @@ -194,7 +216,14 @@ private void checkValue(final RestListParameterValue value) {

@Override
public boolean isValid(ParameterValue value) {
return values.contains(((RestListParameterValue) value).getValue());
if(value == null || value.getValue() == null) {
return false;
}

return values.stream()
.map(ValueItem::getValue)
.filter(Objects::nonNull)
.anyMatch(val -> value.getValue().equals(val));
}

@Override
Expand Down Expand Up @@ -333,6 +362,7 @@ public FormValidation doTestConfiguration(@AncestorInPath final Item context,
@QueryParameter final String credentialId,
@QueryParameter final MimeType mimeType,
@QueryParameter final String valueExpression,
@QueryParameter final String displayExpression,
@QueryParameter final String filter,
@QueryParameter final ValueOrder valueOrder)
{
Expand All @@ -357,24 +387,25 @@ public FormValidation doTestConfiguration(@AncestorInPath final Item context,
return FormValidation.error(Messages.RLP_DescriptorImpl_ValidationErr_ExpressionEmpty());
}

ResultContainer<List<String>> container = RestValueService.get(
ResultContainer<List<ValueItem>> container = RestValueService.get(
restEndpoint,
credentials.orElse(null),
mimeType,
0,
valueExpression,
displayExpression,
filter,
valueOrder);

Optional<String> errorMsg = container.getErrorMsg();
List<String> values = container.getValue();
List<ValueItem> values = container.getValue();
if (errorMsg.isPresent()) {
return FormValidation.error(errorMsg.get());
}

// values should NEVER be empty here
// due to all the filtering and error handling done in the RestValueService
return FormValidation.ok(Messages.RLP_DescriptorImpl_ValidationOk_ConfigValid(values.size(), values.get(0)));
return FormValidation.ok(Messages.RLP_DescriptorImpl_ValidationOk_ConfigValid(values.size(), values.get(0).getDisplayValue()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
public final class RestListParameterValue extends ParameterValue {
@Exported(visibility = 4)
@Restricted(NoExternalUse.class)
public String value;
private final String value;

@DataBoundConstructor
public RestListParameterValue(String name,
String value)
public RestListParameterValue(final String name,
final String value)
{
this(name, value, null);
}

public RestListParameterValue(String name,
String value,
String description)
public RestListParameterValue(final String name,
final String value,
final String description)
{
super(name, description);
this.value = value;
Expand Down Expand Up @@ -87,7 +87,11 @@ public boolean equals(Object obj) {

@Override
public String toString() {
return "(RestListParameterValue) " + getName() + "='" + value + "'";
return "{" +
"\"type\": \"RestListParameterValue\", "+
"\"name:\": \"" + getName() + "\", " +
"\"value\": \"" + value + "\"" +
"}";
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import hudson.util.FormValidation;
import io.jenkins.plugins.restlistparam.Messages;
import io.jenkins.plugins.restlistparam.model.ValueItem;
import io.jenkins.plugins.restlistparam.model.MimeType;
import io.jenkins.plugins.restlistparam.model.ResultContainer;
import io.jenkins.plugins.restlistparam.model.ValueOrder;
Expand Down Expand Up @@ -39,29 +40,31 @@ private RestValueService() {
* This method uses its parameters to query a REST/Web endpoint to receive a {@link MimeType} response, which then
* gets parsed with a supported Path expression to extract a list of string values.
*
* @param restEndpoint A http/https web address to the REST/Web endpoint
* @param credentials The credentials required to access said endpoint
* @param mimeType The MIME type of the expected REST/Web response
* @param cacheTime Time for how long the REST response gets cached for in minutes
* @param expression The Json-Path or xPath expression to filter the values
* @param filter additional regex filter on any parsed values
* @param order Set a {@link ValueOrder} to optionally reorder the values
* @param restEndpoint A http/https web address to the REST/Web endpoint
* @param credentials The credentials required to access said endpoint
* @param mimeType The MIME type of the expected REST/Web response
* @param cacheTime Time for how long the REST response gets cached for in minutes
* @param valueExpression The Json-Path or xPath expression to filter the values
* @param displayExpression The Json-Path or xPath expression to filter the display values
* @param filter additional regex filter on any parsed values
* @param order Set a {@link ValueOrder} to optionally reorder the values
* @return A {@link ResultContainer} that capsules either the desired values or a user friendly error message.
*/
public static ResultContainer<List<String>> get(final String restEndpoint,
final StandardCredentials credentials,
final MimeType mimeType,
final Integer cacheTime,
final String expression,
final String filter,
final ValueOrder order)
public static ResultContainer<List<ValueItem>> get(final String restEndpoint,
final StandardCredentials credentials,
final MimeType mimeType,
final Integer cacheTime,
final String valueExpression,
final String displayExpression,
final String filter,
final ValueOrder order)
{
ResultContainer<List<String>> valueList = new ResultContainer<>(Collections.emptyList());
ResultContainer<List<ValueItem>> valueList = new ResultContainer<>(Collections.emptyList());
ResultContainer<String> rawValues = getValueStringFromRestEndpoint(restEndpoint, credentials, mimeType, cacheTime);
Optional<String> rawValueError = rawValues.getErrorMsg();

if (!rawValueError.isPresent()) {
valueList = convertToValuesList(mimeType, rawValues.getValue(), expression);
valueList = convertToValuesList(mimeType, rawValues.getValue(), valueExpression, displayExpression);
}
else {
valueList.setErrorMsg(rawValueError.get());
Expand Down Expand Up @@ -224,21 +227,23 @@ else if (credentials instanceof StringCredentials) {
*
* @param mimeType The {@link MimeType} of the {@code valueString}
* @param valueString The value string to be parsed
* @param expression The Json-Path or xPath expression to apply on the {@code valueString}
* @param valueExpression The Json-Path or xPath expression to apply on the {@code valueString}
* @param displayExpression Derives the value to be displayed to the user parsed by value expression
* @return A {@link ResultContainer} capsuling the results of the applied expression or an error message
*/
private static ResultContainer<List<String>> convertToValuesList(final MimeType mimeType,
final String valueString,
final String expression)
private static ResultContainer<List<ValueItem>> convertToValuesList(final MimeType mimeType,
final String valueString,
final String valueExpression,
final String displayExpression)
{
ResultContainer<List<String>> container;
ResultContainer<List<ValueItem>> container;

switch (mimeType) {
case APPLICATION_JSON:
container = ValueResolver.resolveJsonPath(valueString, expression);
container = ValueResolver.resolveJsonPath(valueString, valueExpression, displayExpression);
break;
case APPLICATION_XML:
container = ValueResolver.resolveXPath(valueString, expression);
container = ValueResolver.resolveXPath(valueString, valueExpression, displayExpression);
break;
default:
throw new IllegalStateException("Unexpected value: " + mimeType);
Expand All @@ -255,18 +260,18 @@ private static ResultContainer<List<String>> convertToValuesList(final MimeType
* @param order The Order to apply (if any)
* @return A {@link ResultContainer} capsuling a filtered string list or a user friendly error message
*/
private static ResultContainer<List<String>> filterAndSortValues(final List<String> values,
final String filter,
final ValueOrder order)
private static ResultContainer<List<ValueItem>> filterAndSortValues(final List<ValueItem> values,
final String filter,
final ValueOrder order)
{
ResultContainer<List<String>> container = new ResultContainer<>(Collections.emptyList());
ResultContainer<List<ValueItem>> container = new ResultContainer<>(Collections.emptyList());

try {
List<String> updatedValues;
List<ValueItem> updatedValues;

if (isFilterSet(filter) && !isOrderSet(order)) {
updatedValues = values.stream()
.filter(value -> value.matches(filter))
.filter(value -> value.getValue().matches(filter))
.collect(Collectors.toList());
}
else if (!isFilterSet(filter) && isOrderSet(order)) {
Expand All @@ -276,7 +281,7 @@ else if (!isFilterSet(filter) && isOrderSet(order)) {
}
else {
updatedValues = values.stream()
.filter(value -> value.matches(filter))
.filter(value -> value.getValue().matches(filter))
.sorted(order == ValueOrder.ASC ? Comparator.naturalOrder() : Comparator.reverseOrder())
.collect(Collectors.toList());
}
Expand Down
Loading

0 comments on commit 64dac20

Please sign in to comment.