Skip to content

Commit

Permalink
Configuration Docs Linking (#1632)
Browse files Browse the repository at this point in the history
* include linking of filePath and documented objects

* include selectConfigDoc() in UI

* include files to ConfigDocumentation

* fix tests

* display files and enable selection

* include default configs

* prevent ConversionFailedException for placeholders

* refactor AgentConfigurationReloadTask

* add tests

* update documentation

* add parserInstance in InspectitConfigConversionService

* refactor config conversion
  • Loading branch information
EddeCCC authored Jan 8, 2024
1 parent 44f6738 commit 6dcaf70
Show file tree
Hide file tree
Showing 19 changed files with 509 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import inspectit.ocelot.configdocsgenerator.model.*;
import inspectit.ocelot.configdocsgenerator.parsing.ConfigParser;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import rocks.inspectit.ocelot.config.model.InspectitConfig;
Expand Down Expand Up @@ -29,6 +31,7 @@
* Generator for generating ConfigDocumentation objects based on a YAML String describing an {@link InspectitConfig} or the
* InspectitConfig itself.
*/
@Slf4j
public class ConfigDocsGenerator {

/**
Expand All @@ -51,6 +54,12 @@ public class ConfigDocsGenerator {
*/
Map<String, String> specialInputDescriptionsRegex = Collections.singletonMap("_arg\\d", "The _argN-th argument with which the instrumented method was called within which this action is getting executed.");

/**
* Map of files, which contain a set of defined objects like actions, scopes, rules & metrics
*/
@Setter
private Map<String, Set<String>> docsObjectsByFile = new HashMap<>();

/**
* Generates a ConfigDocumentation from a YAML String describing an {@link InspectitConfig}.
*
Expand Down Expand Up @@ -122,7 +131,9 @@ private List<BaseDocs> generateScopeDocs(Map<String, InstrumentationScopeSetting
description = doc.getDescription();
since = doc.getSince();
}
scopeDocs.add(new BaseDocs(scopeName, description, since));
Set<String> files = findFiles(scopeName);

scopeDocs.add(new BaseDocs(scopeName, description, since, files));
}
return scopeDocs;
}
Expand All @@ -146,8 +157,9 @@ private List<MetricDocs> generateMetricDocs(Map<String, MetricDefinitionSettings
description = "";
}
String unit = metricDefinitionSettings.getUnit();
Set<String> files = findFiles(metricName);

metricDocs.add(new MetricDocs(metricName, description, unit));
metricDocs.add(new MetricDocs(metricName, description, unit, files));

}
return metricDocs;
Expand Down Expand Up @@ -180,6 +192,7 @@ private List<ActionDocs> generateActionDocs(Map<String, GenericActionSettings> a
}

Boolean isVoid = actionSettings.getIsVoid();
Set<String> files = findFiles(actionName);

List<ActionInputDocs> inputs = new ArrayList<>();
Map<String, String> inputTypes = actionSettings.getInput();
Expand Down Expand Up @@ -209,11 +222,26 @@ private List<ActionDocs> generateActionDocs(Map<String, GenericActionSettings> a
}
inputs.sort(Comparator.comparing(ActionInputDocs::getName));

actionDocs.add(new ActionDocs(actionName, description, since, inputs, returnDesc, isVoid));
actionDocs.add(new ActionDocs(actionName, description, since, inputs, returnDesc, isVoid, files));
}
return actionDocs;
}

/**
* Finds every file, which contains the definition of a specific object
* @param name the name of the object
* @return a set of files, which contain the provided object
*/
private Set<String> findFiles(String name) {
Set<String> files = docsObjectsByFile.entrySet().stream()
.filter(entry -> entry.getValue().contains(name))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
if(files.isEmpty()) log.warn("No file found with definition of " + name);

return files;
}

/**
* Generates documentation objects for the given actions. Other than the other similar methods in this class,
* the RuleDocs objects are returned in a map, because that map is needed to later call {@link RuleDocs#addActionCallsFromIncludedRules(Map, List)}.
Expand Down Expand Up @@ -261,7 +289,10 @@ private Map<String, RuleDocs> generateRuleDocsMap(Map<String, InstrumentationRul

Map<String, Map<String, ActionCallDocs>> actionCallsMap = generateActionCallDocs(ruleSettings);

ruleDocsMap.put(ruleName, new RuleDocs(ruleName, description, since, includeForDoc, scopesForDoc, ruleMetricsDocs, ruleTracingDocs, actionCallsMap));
Set<String> files = findFiles(ruleName);

ruleDocsMap.put(ruleName, new RuleDocs(ruleName, description, since, includeForDoc, scopesForDoc,
ruleMetricsDocs, ruleTracingDocs, actionCallsMap, files));
}
return ruleDocsMap;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import rocks.inspectit.ocelot.config.model.instrumentation.actions.GenericActionSettings;

import java.util.List;
import java.util.Set;

/**
* Data container for documentation of a single Action's {@link GenericActionSettings} in Config Documentation.
Expand All @@ -26,11 +27,12 @@ public class ActionDocs extends BaseDocs {
*/
private final Boolean isVoid;

public ActionDocs(String name, String description, String since, List<ActionInputDocs> inputs, String returnDescription, Boolean isVoid) {
super(name, description, since);
public ActionDocs(String name, String description, String since, List<ActionInputDocs> inputs, String returnDescription,
Boolean isVoid, Set<String> files) {
super(name, description, since, files);
this.inputs = inputs;
this.returnDescription = returnDescription;
this.isVoid = isVoid;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import lombok.RequiredArgsConstructor;
import rocks.inspectit.ocelot.config.model.instrumentation.documentation.BaseDocumentation;

import java.util.Set;

/**
* Data container for documentation of a single object in Config Documentation, e.g. a scope or action.
*/
Expand All @@ -26,4 +28,9 @@ public class BaseDocs {
*/
private final String since;

}
/**
* File paths, which contain definition for the current object
*/
private final Set<String> files;

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import lombok.Getter;
import rocks.inspectit.ocelot.config.model.metrics.definition.MetricDefinitionSettings;

import java.util.Set;

/**
* Data container for documentation of a single Metric definition's {@link MetricDefinitionSettings} in Config Documentation.
*/
Expand All @@ -14,10 +16,10 @@ public class MetricDocs extends BaseDocs {
*/
private final String unit;

public MetricDocs(String name, String description, String unit) {
public MetricDocs(String name, String description, String unit, Set<String> files) {
// MetricDefinitions do not contain the info for the since field, so it is left empty
super(name, description, null);
super(name, description, null, files);

this.unit = unit;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* Data container for documentation of a single Rule's {@link InstrumentationRuleSettings} in Config Documentation.
Expand Down Expand Up @@ -43,8 +44,10 @@ public class RuleDocs extends BaseDocs {
*/
private Map<String, Map<String, ActionCallDocs>> actionCallsMap;

public RuleDocs(String name, String description, String since, List<String> include, List<String> scopes, List<RuleMetricsDocs> metricsDocs, RuleTracingDocs tracingDoc, Map<String, Map<String, ActionCallDocs>> actionCallsMap) {
super(name, description, since);
public RuleDocs(String name, String description, String since, List<String> include, List<String> scopes,
List<RuleMetricsDocs> metricsDocs, RuleTracingDocs tracingDoc, Map<String, Map<String,
ActionCallDocs>> actionCallsMap, Set<String> files) {
super(name, description, since, files);
this.include = include;
this.scopes = scopes;
this.metricsDocs = metricsDocs;
Expand Down Expand Up @@ -89,4 +92,4 @@ public void addActionCallsFromIncludedRules(Map<String, RuleDocs> allRuleDocs, L
addActionCallsFromIncludedRules(allRuleDocs, includedRule.getInclude());
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public InspectitConfig parseConfig(String configYaml) throws IOException {
log.debug("Successfully deleted temp file '{}' used to generate ConfigDocs.", tempFile.getAbsolutePath());
}

Binder binder = new Binder(configurationPropertySources, new PropertySourcesPlaceholdersResolver(propertySources), InspectitConfigConversionService.getInstance());
Binder binder = new Binder(configurationPropertySources, new PropertySourcesPlaceholdersResolver(propertySources), InspectitConfigConversionService.getParserInstance());

// Create InspectitConfig from ConfigurationPropertySource
return binder.bind("inspectit", InspectitConfig.class).get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ConfigDocsGeneratorTest {

private static MetricDocs metricDoc;

private final ConfigDocsGenerator configDocsGenerator = new ConfigDocsGenerator();
private final static ConfigDocsGenerator configDocsGenerator = new ConfigDocsGenerator();

/**
* Needed to create InspectitConfig Objects to use as input for DocObjectGenerator in tests.
Expand Down Expand Up @@ -355,4 +355,52 @@ void loadAll() throws IOException {
}
}

@Nested
class FindFiles {

@Test
void verifyFindFiles() throws IOException {
String file = "all.yml";
String configYaml = getYaml(file);

Set<String> docsObjects = new HashSet<>();
docsObjects.add(actionWithDocInYaml.getName());
docsObjects.add(actionWithoutDocInYaml.getName());
docsObjects.add(scopeDoc.getName());
docsObjects.add(ruleDocChild.getName());
docsObjects.add(ruleDocParent.getName());
docsObjects.add(metricDoc.getName());

Map<String, Set<String>> docsObjectsByFile = Collections.singletonMap(file, docsObjects);
configDocsGenerator.setDocsObjectsByFile(docsObjectsByFile);
when(actionWithDocInYaml.getFiles()).thenReturn(Collections.singleton(file));
when(actionWithoutDocInYaml.getFiles()).thenReturn(Collections.singleton(file));
when(scopeDoc.getFiles()).thenReturn(Collections.singleton(file));
when(ruleDocChild.getFiles()).thenReturn(Collections.singleton(file));
when(ruleDocParent.getFiles()).thenReturn(Collections.singleton(file));
when(metricDoc.getFiles()).thenReturn(Collections.singleton(file));

InspectitConfig config = configParser.parseConfig(configYaml);
ConfigDocumentation result = configDocsGenerator.generateConfigDocs(config);

List<RuleDocs> rules = new ArrayList<>();
rules.add(ruleDocChild);
rules.add(ruleDocParent);
List<MetricDocs> metrics = new ArrayList<>();
metrics.add(metricDoc);
List<BaseDocs> scopes = new ArrayList<>();
scopes.add(scopeDoc);
List<ActionDocs> actions = new ArrayList<>();
actions.add(actionWithDocInYaml);
actions.add(actionWithoutDocInYaml);

ConfigDocumentation expected = Mockito.mock(ConfigDocumentation.class);
when(expected.getScopes()).thenReturn(scopes);
when(expected.getActions()).thenReturn(actions);
when(expected.getRules()).thenReturn(rules);
when(expected.getMetrics()).thenReturn(metrics);

assertThat(result).usingRecursiveComparison().isEqualTo(expected);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static Map<String, Map<String, ActionCallDocs>> getEmptyActionCallsMap()
* @return RuleDocs object with the given name and otherwise empty values.
*/
private RuleDocs simpleRuleDoc(String name) {
return new RuleDocs(name, "", "", Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), null, Collections.emptyMap());
return new RuleDocs(name, "", "", Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), null, Collections.emptyMap(), Collections.emptySet());
}

/**
Expand All @@ -73,8 +73,8 @@ private RuleDocs simpleRuleDoc(String name) {
private Map<String, RuleDocs> getTestRuleDocs() {
Map<String, RuleDocs> allRuleDocs = new HashMap<>();

/*
RuleDoc1: inherits from RuleDoc2 and 4. Overwrites ActionCall overwritten_by_1 and overwritten_by_1_and_2
/*
RuleDoc1: inherits from RuleDoc2 and 4. Overwrites ActionCall overwritten_by_1 and overwritten_by_1_and_2
which are both also in RuleDoc2.
*/
allRuleDocs.put(RULE_1_NAME, simpleRuleDoc(RULE_1_NAME));
Expand Down Expand Up @@ -111,7 +111,7 @@ private Map<String, RuleDocs> getTestRuleDocs() {
actionCalls2.put(OVERWRITTEN_BY_2, actionCall23);
actionCallsMap2.put(ENTRY_KEY, actionCalls2);
allRuleDocs.get(RULE_2_NAME).setActionCallsMap(actionCallsMap2);

/*
RuleDoc3: Creates ActionCalls overwritten_by_1_and_2, overwritten_by_2 and created_by_3.
*/
Expand Down Expand Up @@ -197,4 +197,4 @@ void addActionCallsFromIncludedRules() {
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ import React from 'react';
import DocsActionElement from './DocsActionElement';
import DocsElementTypes from './DocsElementTypes';
import DocsRuleElement from './DocsRuleElement';
import { configurationActions } from '../../../../redux/ducks/configuration';
import { useDispatch } from 'react-redux';
import { DEFAULT_CONFIG_TREE_KEY } from '../../../../data/constants';

const BaseDocsElement = ({ data, type, registerRef, scrollTo }) => {
const dispatch = useDispatch();
const selectFile = configurationActions.selectFile;

// render specific content based on element type
const renderSpecificContent = (element) => {
switch (type) {
Expand All @@ -18,6 +24,10 @@ const BaseDocsElement = ({ data, type, registerRef, scrollTo }) => {
}
};

const handleClick = (file) => {
dispatch(selectFile(file));
};

return (
<>
<style jsx>{`
Expand Down Expand Up @@ -50,6 +60,19 @@ const BaseDocsElement = ({ data, type, registerRef, scrollTo }) => {
color: #333;
white-space: pre-wrap;
}
.headline {
margin-top: 0.5rem;
margin-bottom: 0.25rem;
font-weight: bold;
color: #4e4e4e;
}
.file-item {
cursor: pointer;
color: #0070f3;
}
.file-item:hover {
text-decoration: underline;
}
`}</style>
{data.map((element) => (
<div
Expand All @@ -71,6 +94,18 @@ const BaseDocsElement = ({ data, type, registerRef, scrollTo }) => {
{element.description && <div className="description">{element.description}</div>}
{renderSpecificContent(element)}
</div>
<div>
{element.files && (
<div className="files">
<div className="headline">Files</div>
{element.files.map((file, index) => (
<div key={index} className="file-item" onClick={() => handleClick(file)}>
{file.replace(DEFAULT_CONFIG_TREE_KEY, 'Ocelot Defaults')}
</div>
))}
</div>
)}
</div>
</div>
))}
</>
Expand Down
Loading

0 comments on commit 6dcaf70

Please sign in to comment.