Skip to content
New issue

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

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

Already on GitHub? # to your account

Closes #703 - Visual editor of the configuration UI shows possible values of Enum properties #878

Merged
merged 4 commits into from
Feb 25, 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { Menubar } from 'primereact/menubar';
import { Message } from 'primereact/message';
import { Row } from 'primereact/row';
import { TreeTable } from 'primereact/treetable';
import { Dropdown } from 'primereact/dropdown';
import PropTypes from 'prop-types';
import React from 'react';
import axios from '../../lib/axios-api';

// helper for a schema property type constants
const schemaType = {
Expand Down Expand Up @@ -49,6 +51,8 @@ class TreeTableEditor extends React.Component {
data: undefined,
expandedKeys: DEFAULT_EXPANDED_KEYS,
showAll: true,
enumOptions: [],
oldNode: '',
};
}

Expand Down Expand Up @@ -329,6 +333,21 @@ class TreeTableEditor extends React.Component {
wrapWithExtras={this.wrapWithExtras}
/>
);
case schemaType.ENUM:
if (this.state.oldNode.key != node.key) {
this.fetchDropdownOptions(node.key, type);
this.setState({ oldNode: node, enumOptions: [] });
}
return (
<EnumEditor
node={node}
options={this.state.enumOptions}
loadingMessage={'loading...'}
onPropValueChange={this.onPropValueChange}
onPropValueRemove={this.onPropValueRemove}
wrapWithExtras={this.wrapWithExtras}
/>
);
case schemaType.INTEGER:
return (
<NumberEditor
Expand Down Expand Up @@ -362,6 +381,31 @@ class TreeTableEditor extends React.Component {
}
};

fetchDropdownOptions = (configPath, schemaType) => {
if (schemaType == 'ENUM') {
axios
.post('/autocomplete', {
path: configPath,
})
.then((response) => {
const suggestions = response.data;
if (suggestions) {
const result = suggestions.map((suggestion) => {
return {
key: suggestion,
value: suggestion,
};
});

this.setState({ enumOptions: result });
}
})
.catch((error) => {
console.warn('Could not fetch autocompletion results.', error);
});
}
};

render() {
const { loading } = this.props;

Expand Down Expand Up @@ -464,6 +508,55 @@ var StringEditor = ({ node, onPropValueChange, onPropValueRemove, wrapWithExtras
}
};

/** Editor for Enums */
var EnumEditor = ({ node, options, loadingMessage, onPropValueChange, onPropValueRemove, wrapWithExtras }) => {
const defaultValue = loadingMessage;
let disable = !options.length;
let currentValue = node.value;
if (disable) {
options.push({
key: loadingMessage,
value: loadingMessage,
});
currentValue = loadingMessage;
}

const onChange = (e) => {
onPropValueChange(node.key, e.value);
};
// component to render
const component = () => (
<Dropdown
value={currentValue}
options={options}
optionLabel={'key'}
optionValue={'value'}
onChange={onChange}
disabled={
disable ||
JSON.stringify(options) ===
JSON.stringify([
{
key: loadingMessage,
value: loadingMessage,
},
])
}
/>
);

if (!wrapWithExtras) {
return component();
} else {
return wrapWithExtras(component, {
node,
defaultSupplier: () => (defaultValue && defaultValue) || 0,
onPropValueChange,
onPropValueRemove,
});
}
};

/** Editor for numbers */
var NumberEditor = ({ node, integer, onPropValueChange, onPropValueRemove, wrapWithExtras }) => {
const defaultValue = node.value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,19 @@ public List<String> getSuggestions(List<String> path) {
* Returns the names of the properties in a given path
*
* @param propertyPath The path to a property one wants to recieve the properties of
*
* @return The names of the properties of the given path as list
*/
private List<String> collectProperties(List<String> propertyPath) {
Type endType = PropertyPathHelper.getPathEndType(propertyPath, InspectitConfig.class);
if (CollectionUtils.isEmpty(propertyPath) || ((propertyPath.size() == 1) && propertyPath.get(0).equals(""))) {
return getProperties(InspectitConfig.class);
}
if (endType instanceof Class<?> && ((Class<?>) endType).isEnum()) {
return Arrays.stream(((Class<?>) endType).getEnumConstants())
.map(Object::toString)
.collect(Collectors.toList());
}
if (endType == null || PropertyPathHelper.isTerminal(endType) || PropertyPathHelper.isListOfTerminalTypes(endType) || !(endType instanceof Class<?>)) {
return Collections.emptyList();
}
Expand All @@ -50,6 +56,7 @@ private List<String> collectProperties(List<String> propertyPath) {
* Return the properties of a given class
*
* @param beanClass the class one wants the properties of
*
* @return the properties of the given class
*/
@VisibleForTesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,7 @@ void checkFirstLevel() {

List<String> result = completer.getSuggestions(input);

assertThat(result).containsExactlyInAnyOrder(
"actions",
"data",
"exclude-lambdas",
"ignored-bootstrap-packages",
"ignored-packages",
"internal",
"rules",
"scopes",
"special");
assertThat(result).containsExactlyInAnyOrder("actions", "data", "exclude-lambdas", "ignored-bootstrap-packages", "ignored-packages", "internal", "rules", "scopes", "special");
}

@Test
Expand All @@ -61,21 +52,7 @@ void pastList() {

List<String> result = completer.getSuggestions(input);

assertThat(result).containsExactlyInAnyOrder(
"config",
"env",
"exporters",
"instrumentation",
"logging",
"metrics",
"plugins",
"privacy",
"publish-open-census-to-bootstrap",
"self-monitoring",
"service-name",
"tags",
"thread-pool-size",
"tracing");
assertThat(result).containsExactlyInAnyOrder("config", "env", "exporters", "instrumentation", "logging", "metrics", "plugins", "privacy", "publish-open-census-to-bootstrap", "self-monitoring", "service-name", "tags", "thread-pool-size", "tracing");
}

@Test
Expand All @@ -87,6 +64,19 @@ void endsInWildcard() {
assertThat(result).isEmpty();
}

@Test
void endsInEnum() {
List<String> input = Arrays.asList("inspectit", "tracing", "add-common-tags");

List<String> result = completer.getSuggestions(input);

assertThat(result).hasSize(4);
assertThat(result).contains("NEVER");
assertThat(result).contains("ON_GLOBAL_ROOT");
assertThat(result).contains("ON_LOCAL_ROOT");
assertThat(result).contains("ALWAYS");
}

@Test
void propertyIsPresentAndReadMethodIsNull() {
List<String> input = Arrays.asList("inspectit", "instrumentation", "data", "method_duration", "is-tag");
Expand Down Expand Up @@ -149,21 +139,7 @@ public class GetProperties {
void getPropertiesInspectit() {
List<String> result = completer.getProperties(InspectitConfig.class);

assertThat(result).containsExactlyInAnyOrder(
"config",
"env",
"exporters",
"instrumentation",
"logging",
"metrics",
"plugins",
"privacy",
"publish-open-census-to-bootstrap",
"self-monitoring",
"service-name",
"tags",
"thread-pool-size",
"tracing");
assertThat(result).containsExactlyInAnyOrder("config", "env", "exporters", "instrumentation", "logging", "metrics", "plugins", "privacy", "publish-open-census-to-bootstrap", "self-monitoring", "service-name", "tags", "thread-pool-size", "tracing");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@ public class PropertyPathHelper {
* A HashSet of classes which are used as wildcards in the search for properties. If a found class matches one of these
* classes, the end of the property path is reached. Mainly used in the search of maps
*/
private static final HashSet<Class<?>> TERMINAL_TYPES = new HashSet<>(Arrays.asList(Object.class, String.class, Integer.class, Long.class,
Float.class, Double.class, Character.class, Void.class,
Boolean.class, Byte.class, Short.class, Duration.class, Path.class, URL.class, FileSystemResource.class));
private static final HashSet<Class<?>> TERMINAL_TYPES = new HashSet<>(Arrays.asList(Object.class, String.class, Integer.class, Long.class, Float.class, Double.class, Character.class, Void.class, Boolean.class, Byte.class, Short.class, Duration.class, Path.class, URL.class, FileSystemResource.class));

/**
* Returns the type which can be found at the end of the path. Returns null if the path does not exist
*
* @param propertyNames The list of properties one wants to check
* @param type The type in which the current top-level properties should be found
*
* @return The type which can be found at the end of the path. Returns null if the path does not exist
*/
public Type getPathEndType(List<String> propertyNames, Type type) {
Expand All @@ -57,6 +56,7 @@ public Type getPathEndType(List<String> propertyNames, Type type) {
*
* @param propertyNames List of property names
* @param mapValueType The type which is given as value type of a map
*
* @return True: The type exists <br> False: the type does not exists
*/
Type getTypeInMap(List<String> propertyNames, Type mapValueType) {
Expand All @@ -72,6 +72,7 @@ Type getTypeInMap(List<String> propertyNames, Type mapValueType) {
*
* @param propertyNames List of property names
* @param listValueType The type which is given as value type of a list
*
* @return True: The type exists <br> False: the type does not exists
*/
Type getTypeInList(List<String> propertyNames, Type listValueType) {
Expand All @@ -83,14 +84,14 @@ Type getTypeInList(List<String> propertyNames, Type listValueType) {
*
* @param propertyNames List of property names
* @param beanType The bean through which should be searched
*
* @return True: the property and all other properties exists <br> False: At least one of the properties does not exist
*/
private Type getTypeInBean(List<String> propertyNames, Class<?> beanType) {
String propertyName = CaseUtils.kebabCaseToCamelCase(propertyNames.get(0));
Optional<PropertyDescriptor> foundProperty =
Arrays.stream(BeanUtils.getPropertyDescriptors(beanType))
.filter(descriptor -> CaseUtils.compareIgnoreCamelOrKebabCase(propertyName, descriptor.getName()))
.findFirst();
Optional<PropertyDescriptor> foundProperty = Arrays.stream(BeanUtils.getPropertyDescriptors(beanType))
.filter(descriptor -> CaseUtils.compareIgnoreCamelOrKebabCase(propertyName, descriptor.getName()))
.findFirst();
if (foundProperty.isPresent()) {
Type propertyType;
Method writeMethod = foundProperty.get().getWriteMethod();
Expand All @@ -108,16 +109,16 @@ private Type getTypeInBean(List<String> propertyNames, Class<?> beanType) {
* Checks if a given type is a terminal type or an enum.
*
* @param type The type to be checked.
*
* @return True: the given type is a terminal or an enum. False: the given type is neither a terminal type nor an enum.
*/
public boolean isTerminal(Type type) {
if (TERMINAL_TYPES.contains(type)) {
return true;
} else if (type instanceof Class) {
return ((Class<?>) type).isEnum()
|| ((Class<?>) type).isPrimitive()
|| ApplicationConversionService.getSharedInstance().canConvert(String.class, (Class<?>) type)
|| ApplicationConversionService.getSharedInstance().canConvert(Number.class, (Class<?>) type);
return ((Class<?>) type).isEnum() || ((Class<?>) type).isPrimitive() || ApplicationConversionService.getSharedInstance()
.canConvert(String.class, (Class<?>) type) || (ApplicationConversionService.getSharedInstance()
.canConvert(Number.class, (Class<?>) type));
}
return false;
}
Expand All @@ -127,6 +128,7 @@ public boolean isTerminal(Type type) {
* Every class which is no Collection, Map or terminal (see {@link #isTerminal(Type)} is classified as POJO.
*
* @param type the type to check
*
* @return true if the given type is a pojo.
*/
public boolean isBean(Type type) {
Expand All @@ -149,6 +151,7 @@ public boolean isBean(Type type) {
* Checks if a given type is a list of terminal types
*
* @param type
*
* @return True: the given type is a list of a terminal type. False: either the given type is not a list or not a list of terminal types
*/
public boolean isListOfTerminalTypes(Type type) {
Expand All @@ -161,13 +164,13 @@ public boolean isListOfTerminalTypes(Type type) {
return false;
}


/**
* This method takes an array of strings and returns each entry as ArrayList containing the parts of each element.
* <p>
* 'inspectit.hello-i-am-testing' would be returned as {'inspectit', 'helloIAmTesting'}
*
* @param propertyName A String containing the property path
*
* @return a List containing containing the parts of the property path as String
*/
public List<String> parse(String propertyName) {
Expand All @@ -190,6 +193,7 @@ public List<String> parse(String propertyName) {
*
* @param propertyName A String with the path of a property
* @param result Reference to the list in which the extracted expressions should be saved in
*
* @return the remaining expression
*/
private String extractExpression(String propertyName, List<String> result) {
Expand Down Expand Up @@ -237,13 +241,13 @@ private String removeLeadingDot(String string) {
}
}


/**
* Checks if two paths are the same. If one path uses the Wildcard "*", the check with the corresponding literal in the
* other path return true.
*
* @param pathA the first path to be compared
* @param pathB the second path to be compared
*
* @return
*/
public boolean comparePaths(List<String> pathA, List<String> pathB) {
Expand All @@ -265,6 +269,7 @@ public boolean comparePaths(List<String> pathA, List<String> pathB) {
*
* @param a the first path to be compared
* @param a the second path to be compared
*
* @return Returns true if each String in the two paths is equal.
*/
public boolean comparePathsIgnoreCamelOrKebabCase(List<String> a, List<String> b) {
Expand All @@ -279,7 +284,6 @@ public boolean comparePathsIgnoreCamelOrKebabCase(List<String> a, List<String> b
return true;
}


/**
* Checks if the first given path starts with the second given full path
* <p>
Expand All @@ -289,6 +293,7 @@ public boolean comparePathsIgnoreCamelOrKebabCase(List<String> a, List<String> b
*
* @param path The path you want to check
* @param prefix The prefix the other path should begin with
*
* @return
*/
public boolean hasPathPrefix(List<String> path, List<String> prefix) {
Expand Down