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

feat: display value selector #85

Merged
merged 9 commits into from
Oct 21, 2021
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
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