Skip to content

Commit

Permalink
Closes #1070 - Implementation of a formatted view for scopes in the g…
Browse files Browse the repository at this point in the history
…raphical configuration view (#1096)
  • Loading branch information
mariusoe committed Jun 2, 2021
1 parent b4c167f commit b8e0eaf
Show file tree
Hide file tree
Showing 10 changed files with 413 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React from 'react';
import { Provider } from 'react-redux';
import createStore from '../src/redux/store';

// styles for primereact
import 'primereact/resources/themes/nova-dark/theme.css';
import 'primereact/resources/primereact.min.css';
import 'primeicons/primeicons.css';
import 'primeflex/primeflex.css';

const store = createStore({}, false);

// global decorator for styles from '_app.js'
export const decorators = [
(Story) => (
Expand All @@ -18,7 +22,9 @@ export const decorators = [
}
`}
</style>
<Story />
<Provider store={store}>
<Story />
</Provider>
</div>
),
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import PropTypes from 'prop-types';

const AVAILABLE_THEMES = ['gray', 'blue', 'yellow', 'green'];

/**
* Component for showing a highlighted text element.
*/
const HighlightText = ({ value, theme }) => {
const themeClass = AVAILABLE_THEMES.includes(theme) ? theme : AVAILABLE_THEMES[0];

return (
<>
<style jsx>{`
span {
font-family: monospace;
padding: 0.1rem 0.5rem;
border-radius: 0.2rem;
font-weight: 700;
}
.gray {
color: #333333;
background-color: #e6e6e6;
}
.blue {
color: #466594;
background-color: #cfe2ff;
}
.yellow {
color: #805b36;
background-color: #ffd8b2;
}
.green {
color: #256029;
background-color: #c8e6c9;
}
`}</style>
<span className={themeClass}>{value}</span>
</>
);
};

HighlightText.propTypes = {
/** The text to show. */
value: PropTypes.string,
/** The color theme to use */
theme: PropTypes.oneOf(AVAILABLE_THEMES),
};

HighlightText.defaultProps = {
value: null,
theme: 'gray',
};

export default HighlightText;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';

import HighlightText from './HighlightText';

export default {
title: 'Components/Editor/MethodConfigurationEditor/HighlightText',
component: HighlightText,
};

const Template = (args) => <HighlightText {...args} />;

export const Default = Template.bind({});
Default.args = {
value: 'Hello World!',
};

export const Blue = Template.bind({});
Blue.args = {
value: 'Hello World!',
theme: 'blue',
};

export const Yellow = Template.bind({});
Yellow.args = {
value: 'Hello World!',
theme: 'yellow',
};

export const Green = Template.bind({});
Green.args = {
value: 'Hello World!',
theme: 'green',
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import _ from 'lodash';
import SelectionInformation from '../SelectionInformation';
import ErrorInformation from '../ErrorInformation';
import { TOOLTIP_OPTIONS } from '../../../data/constants';
import ScopeTypeDisplay from './ScopeTypeDisplay';
import ScopeMethodDisplay from './ScopeMethodDisplay';
import { selectedFileContentsChanged } from '../../../redux/ducks/configuration/actions';
import { useDispatch } from 'react-redux';

Expand Down Expand Up @@ -122,15 +124,15 @@ const MethodConfigurationEditor = ({ yamlConfiguration }) => {
/**
* Providing the template body for the row group header.
*/
const rowGroupHeaderTemplate = ({ typeKey }) => {
return 'Target: ' + typeKey;
const rowGroupHeaderTemplate = ({ scope }) => {
return <ScopeTypeDisplay scope={scope} />;
};

/**
* Providing the template body for the scopes.
*/
const scopeDescriptionBodyTemplate = ({ scope }) => {
return JSON.stringify(scope);
return <ScopeMethodDisplay scope={scope} />;
};

/**
Expand Down Expand Up @@ -194,16 +196,25 @@ const MethodConfigurationEditor = ({ yamlConfiguration }) => {
border: none;
}
.this :global(.p-datatable tr) {
.this :global(.p-datatable tr.p-rowgroup-header) {
border-top: 1px solid #e7e7e7;
}
.this :global(.p-datatable .p-rowgroup-header td) {
padding: 1rem 0.5rem;
}
.this :global(.p-datatable tr.p-rowgroup-footer) {
height: 1rem;
}
.this :global(.p-datatable-row td:first-of-type) {
padding-left: 2.25rem;
padding-left: 2.5rem;
line-height: 1.4rem;
}
.this :global(.p-datatable-row td) {
padding-top: 0.15rem;
padding-bottom: 0.15rem;
}
/* prevent different backgrounds for even and odd row numbers*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const Template = (args) => <MethodConfigurationEditor {...args} />;
export const Default = Template.bind({});
Default.args = {
yamlConfiguration:
"# {\"type\": \"Method-Configuration\"}\ninspectit:\n instrumentation:\n scopes:\n 's_apacheclient_doExecute':\n interfaces:\n - name: 'org.apache.http.impl.client.CloseableHttpClient'\n methods:\n - name: 'doExecute'\n \n 's_httpurlconnection_getOutputStream':\n type:\n name: 'java.net.HttpURLConnection'\n methods:\n - name: 'getOutputStream'\n arguments: []\n\n 's_httpurlconnection_getInputStream':\n superclass:\n name: 'java.net.HttpURLConnection'\n methods:\n - name: 'getInputStream'\n arguments: []\n\n 's_httpurlconnection_connect':\n superclass:\n name: 'java.net.HttpURLConnection'\n methods:\n - name: 'connect'\n arguments: []\n\n 's_httpurlconnection_requestInitiators':\n superclass:\n name: 'java.net.HttpURLConnection'\n methods:\n - name: 'getHeaderField'\n - name: 'getHeaderFieldDate'\n - name: 'getHeaderFieldKey'\n - name: 'getResponseCode'\n - name: 'getResponseMessage'",
"# {\"type\": \"Method-Configuration\"}\ninspectit:\n instrumentation:\n scopes:\n 's_apacheclient_doExecute':\n interfaces:\n - name: 'org.apache.http.impl.client.CloseableHttpClient'\n matcher-mode: ENDS_WITH_IGNORE_CASE\n methods:\n - name: 'doExecute'\n \n 's_httpurlconnection_getOutputStream':\n type:\n name: 'java.net.HttpURLConnection'\n methods:\n - name: 'getOutputStream'\n arguments: []\n\n 's_httpurlconnection_getInputStream':\n superclass:\n name: 'java.net.HttpURLConnection'\n methods:\n - name: 'getInputStream'\n arguments: []\n\n 's_httpurlconnection_connect':\n superclass:\n name: 'java.net.HttpURLConnection'\n methods:\n - name: 'connect'\n arguments: []\n\n 's_httpurlconnection_requestInitiators':\n superclass:\n name: 'java.net.HttpURLConnection'\n Matcher-Mode: EQUALS_FULLY\n methods:\n - name: 'getHeaderField'\n arguments: ['java.lang.Object', 'java.lang.Object']\n Matcher-Mode: STARTS_WITH_IGNORE_CASE\n visibility: ['PUBLIC', 'PRIVATE']",
};

export const Empty = Template.bind({});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react';
import PropTypes from 'prop-types';
import HighlightText from './HighlightText';
import _ from 'lodash';
import { MATCHER_MODE_DESCRIPTION, DEFAULT_VISIBILITIES } from './constants';

/**
* Utility function for finding (case-insensitive) a specific attribute of a given object.
*
* @param {*} object the object being searched
* @param {*} findKey the name of the attribute to find
*/
const findIgnoreCase = (object, findKey) => {
return _.find(object, (_value, key) => {
return key.toLowerCase() === findKey;
});
};

/**
* Component for displaying the method matchers of given scope in a nice representation.
*/
const ScopeMethodDisplay = ({ scope }) => {
const { methods } = scope;

// In case no method matcher exists, all methods will be used.
if (!methods || methods.length === 0) {
return <span className="p-component">All existing methods</span>;
}

// The editor does not support scopes targeting multiple methods.
// Individual scopes must be used for this purpose.
if (methods.length !== 1) {
throw new Error('Multi-method scopes are currently not supported.');
}

// the method matcher which will be shown
const method = methods[0];

// defining the visual elements for the matcher's type and name
let methodTypeDescriptor;
let nameDescriptor;
const constructor = findIgnoreCase(method, 'is-constructor');
if (constructor) {
// in case constructors should be matched
methodTypeDescriptor = <>Constructors</>;
// nameDescriptor not needed for constructors
} else {
// in case methods should be matched
methodTypeDescriptor = <>Methods</>;
// show name only if defined
const name = findIgnoreCase(method, 'name');
if (name) {
const matcherMode = findIgnoreCase(method, 'matcher-mode');
const matcherText = _.get(MATCHER_MODE_DESCRIPTION, matcherMode, MATCHER_MODE_DESCRIPTION.EQUALS_FULLY);

nameDescriptor = (
<>
whose names <HighlightText value={matcherText} theme="blue" /> <HighlightText value={name} />
</>
);
}
}

// defining the visual elements for the matcher's visibility
let visibilityDescriptor;
const visibility = findIgnoreCase(method, 'visibility');
// show visibility only if defined
if (!_.isEmpty(visibility)) {
const isDefaultVisibility = _.isEmpty(_.difference(DEFAULT_VISIBILITIES, visibility));

// show visibility only when it differs from the default values
if (!isDefaultVisibility) {
const visibilities = _.join(visibility, ' OR ');
visibilityDescriptor = (
<>
with visibilities of <HighlightText value={visibilities} theme="green" />
</>
);
}
}

// defining the visual elements for the matcher's arguments
let argumentDescriptor;
const methodArguments = findIgnoreCase(method, 'arguments');
if (_.isNil(methodArguments)) {
// show nothing when it is not defined
} else if (_.isEmpty(methodArguments)) {
// in case an empty array is specified
argumentDescriptor = <>without arguments</>;
} else {
// in case arguments are specified - join them together
const argumentsString = _.join(methodArguments, ', ');
argumentDescriptor = (
<>
with arguments <HighlightText value={argumentsString} theme="yellow" />
</>
);
}

return (
<span className="p-component">
{methodTypeDescriptor} {nameDescriptor} {visibilityDescriptor} {argumentDescriptor}
</span>
);
};

ScopeMethodDisplay.propTypes = {
/** The scopes to visualize. */
scope: PropTypes.object,
};

ScopeMethodDisplay.defaultProps = {
scope: {},
};

export default ScopeMethodDisplay;
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';

import ScopeMethodDisplay from './ScopeMethodDisplay';

export default {
title: 'Components/Editor/MethodConfigurationEditor/ScopeMethodDisplay',
component: ScopeMethodDisplay,
};

const Template = (args) => <ScopeMethodDisplay {...args} />;

export const AllMethods = Template.bind({});
AllMethods.args = {
scope: { superclass: { name: 'java.net.HttpURLConnection', 'MATCHER-MODE': 'ENDS_WITH_IGNORE_CASE' }, methods: [] },
};

export const OnlyName = Template.bind({});
OnlyName.args = {
scope: {
superclass: { name: 'java.net.HttpURLConnection', 'MATCHER-MODE': 'ENDS_WITH_IGNORE_CASE' },
methods: [{ name: 'getInputStream' }],
},
};

export const NameMatches = Template.bind({});
NameMatches.args = {
scope: {
type: { name: 'org.apache.http.impl.client.CloseableHttpClient', 'Matcher-Mode': 'MATCHES' },
methods: [{ name: 'getInputStream', 'Matcher-Mode': 'STARTS_WITH_IGNORE_CASE' }],
},
};

export const ByVisibility = Template.bind({});
ByVisibility.args = {
scope: {
interfaces: [{ name: 'org.apache.http.impl.client.CloseableHttpClient' }],
methods: [{ visibility: ['PUBLIC', 'PRIVATE'] }],
},
};

export const AllVisibilities = Template.bind({});
AllVisibilities.args = {
scope: {
interfaces: [{ name: 'org.apache.http.impl.client.CloseableHttpClient' }],
methods: [{ name: 'getInputStream', visibility: ['PUBLIC', 'PRIVATE', 'PROTECTED', 'PACKAGE'] }],
},
};

export const WithArguments = Template.bind({});
WithArguments.args = {
scope: {
interfaces: [{ name: 'org.apache.http.impl.client.CloseableHttpClient' }],
methods: [{ name: 'getInputStream', arguments: ['java.lang.Object', 'java.lang.Object'] }],
},
};

export const WithoutArguments = Template.bind({});
WithoutArguments.args = {
scope: {
interfaces: [{ name: 'org.apache.http.impl.client.CloseableHttpClient' }],
methods: [{ name: 'getInputStream', arguments: [] }],
},
};

export const IsConstructor = Template.bind({});
IsConstructor.args = {
scope: {
interfaces: [{ name: 'org.apache.http.impl.client.CloseableHttpClient' }],
methods: [{ 'is-constructor': true, arguments: [], visibility: ['PUBLIC'] }],
},
};

0 comments on commit b8e0eaf

Please sign in to comment.