Merge pull request #319 from CatalystCode/add-extension-docs
adding documentation
This commit is contained in:
Коммит
eead308150
|
@ -45,9 +45,8 @@ yarn start:dev
|
|||
|
||||
Open **http://localhost:3000**
|
||||
|
||||
For contribution and code documentation, [follow this link](/docs/README.md).
|
||||
|
||||
(For more information on development environment, see https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/)
|
||||
For contribution and code documentation follow:
|
||||
[DEVELOPMENT & CONTRIBUTION](/docs/README.md).
|
||||
|
||||
# Deploy To Azure
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugi
|
|||
* type: 'bars',
|
||||
* args: {
|
||||
* prefix: string - a prefix string for the exported variables (default to id).
|
||||
* data: string - the state property holding the data (default is 'values').
|
||||
* valueField: string - The field name holding the value/y value of the bar
|
||||
* barsField: string - The field name holding the names for the bars
|
||||
* seriesField: string - The field name holding the series name (aggregation in a specific field)
|
||||
|
@ -63,7 +64,7 @@ export function bars(
|
|||
const threshold = args.threshold || 0;
|
||||
const othersValue = args.othersValue || 'Others';
|
||||
|
||||
let values: any[] = state.values || [];
|
||||
let values: any[] = state[args.data || 'values'] || [];
|
||||
|
||||
// Concating values with '...'
|
||||
if (values && values.length && valueMaxLength && (seriesField || barsField)) {
|
||||
|
|
|
@ -25,6 +25,7 @@ import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugi
|
|||
* type: 'filter',
|
||||
* args: {
|
||||
* prefix: string - a prefix string for the exported variables (default to id).
|
||||
* data: string - the state property holding the data (default is 'values').
|
||||
* field: string - the field holding the filter values in the results (default = "value")
|
||||
* }
|
||||
* }
|
||||
|
@ -40,14 +41,14 @@ export function filter (
|
|||
plugin: IDataSourcePlugin,
|
||||
prevState: any) {
|
||||
|
||||
const { values } = state;
|
||||
if (!values) { return null; }
|
||||
|
||||
const args = typeof format !== 'string' ? format.args : {};
|
||||
const prefix = getPrefix(format);
|
||||
const field = args.field || 'value';
|
||||
const unknown = args.unknown || 'unknown';
|
||||
|
||||
const values = state[args.data || 'values'];
|
||||
if (!values) { return null; }
|
||||
|
||||
// This code is meant to fix the following scenario:
|
||||
// When "Timespan" filter changes, to "channels-selected" variable
|
||||
// is going to be reset into an empty set.
|
||||
|
|
|
@ -11,6 +11,7 @@ import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugi
|
|||
* type: 'filtered_samples',
|
||||
* args: {
|
||||
* prefix: string - a prefix string for the filtered sample data (defaults to 'filtered').
|
||||
* data: string - the state property holding the data (default is 'values').
|
||||
* }
|
||||
* }
|
||||
* @param state Current received state from data source
|
||||
|
|
|
@ -26,6 +26,7 @@ import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugi
|
|||
* type: 'filter',
|
||||
* args: {
|
||||
* prefix: string - a prefix string for the exported variables (default to id).
|
||||
* data: string - the state property holding the data (default is 'values').
|
||||
* }
|
||||
* }
|
||||
* @param state Current received state from data source
|
||||
|
@ -41,17 +42,20 @@ export function flags(
|
|||
prevState: any) {
|
||||
|
||||
const prefix = getPrefix(format);
|
||||
const args = typeof format !== 'string' && format.args || {};
|
||||
const params = plugin.getParams();
|
||||
|
||||
if (!params || !Array.isArray(params.values)) {
|
||||
return formatWarn('A paramerter "values" is expected as an array on "params" in the data source', 'filter', plugin);
|
||||
}
|
||||
|
||||
if (!state) { return null; }
|
||||
|
||||
let values = params[args.data || 'values'];
|
||||
let flags = {};
|
||||
params.values.forEach(key => { flags[key] = state.selectedValue === key; });
|
||||
values.forEach(key => { flags[key] = state.selectedValue === key; });
|
||||
|
||||
flags[prefix + 'values-all'] = params.values;
|
||||
flags[prefix + 'values-all'] = values;
|
||||
flags[prefix + 'values-selected'] = state.selectedValue || [];
|
||||
|
||||
return flags;
|
||||
|
|
|
@ -43,7 +43,13 @@ import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugi
|
|||
* ]
|
||||
* }
|
||||
*
|
||||
* @param format Plugin format parameter
|
||||
* @param format 'retention' | {
|
||||
* type: 'retention',
|
||||
* args: {
|
||||
* prefix: string - a prefix string for the exported variables (default to id).
|
||||
* data: string - the state property holding the data (default is 'values').
|
||||
* }
|
||||
* }
|
||||
* @param state Current received state from data source
|
||||
* @param dependencies Dependencies for the plugin
|
||||
* should contain "selectedTimespan" equals to 'PT24H', 'P7D' etc...
|
||||
|
@ -55,12 +61,15 @@ export function retention (
|
|||
dependencies: IDictionary,
|
||||
plugin: IDataSourcePlugin,
|
||||
prevState: any) {
|
||||
const { values } = state;
|
||||
const { selectedTimespan } = dependencies;
|
||||
|
||||
if (!values || !values.length) { return null; }
|
||||
|
||||
const prefix = getPrefix(format);
|
||||
const args = typeof format !== 'string' && format.args || {};
|
||||
|
||||
let values = state[args.data || 'values'];
|
||||
if (!values || !values.length) { return null; }
|
||||
|
||||
const { selectedTimespan } = dependencies;
|
||||
|
||||
let result = {
|
||||
totalUnique: 0,
|
||||
totalUniqueUsersIn24hr: 0,
|
||||
|
|
|
@ -25,6 +25,7 @@ import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugi
|
|||
* type: 'scorecard',
|
||||
* args: {
|
||||
* prefix: string - a prefix string for the exported variables (default to id).
|
||||
* data: string - the state property holding the data (default is 'values').
|
||||
* countField: 'count' - Field name with count value (default: 'count')
|
||||
* postfix: '%' - String to add after the value (default: null)
|
||||
* thresholds: [{ value: 0, heading: '', color: '#000', icon: 'done' }]
|
||||
|
@ -43,11 +44,12 @@ export function scorecard (
|
|||
dependencies: IDictionary,
|
||||
plugin: IDataSourcePlugin,
|
||||
prevState: any) {
|
||||
let { values } = state;
|
||||
|
||||
const args = typeof format !== 'string' && format.args || { thresholds: null };
|
||||
const countField = args.countField || 'count';
|
||||
const postfix = args.postfix || null;
|
||||
let values = state[args.data || 'values'];
|
||||
|
||||
let checkValue = (values && values[0] && values[0][countField]) || 0;
|
||||
|
||||
let createValue = (value: any, heading: string, color: string, icon: string, subvalue?: any, subheading?: string) => {
|
||||
|
|
|
@ -35,6 +35,7 @@ import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugi
|
|||
* type: 'timeline',
|
||||
* args: {
|
||||
* prefix: string - a prefix string for the exported variables (default to id).
|
||||
* data: string - the state property holding the data (default is 'values').
|
||||
* timeField: 'timestamp' - The field containing timestamp
|
||||
* lineField: 'channel' - A field to hold/group by different lines in the graph
|
||||
* valueField: 'count' - holds the value/y value of the current point
|
||||
|
@ -56,11 +57,12 @@ export function timeline(
|
|||
return formatWarn('format should be an object with args', 'timeline', plugin);
|
||||
}
|
||||
|
||||
const timeline = state.values;
|
||||
const { timespan } = dependencies;
|
||||
const args = format.args || {};
|
||||
const { timeField, lineField, valueField } = args;
|
||||
const prefix = getPrefix(format);
|
||||
let values = state[args.data || 'values'];
|
||||
const timeline = values;
|
||||
|
||||
let _timeline = {};
|
||||
let _lines = {};
|
||||
|
|
|
@ -26,6 +26,7 @@ import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugi
|
|||
* type: 'timespan',
|
||||
* args: {
|
||||
* prefix: string - a prefix string for the exported variables (default to id).
|
||||
* data: string - the state property holding the data (default is 'values').
|
||||
* }
|
||||
* }
|
||||
* @param state Current received state from data source
|
||||
|
@ -58,7 +59,11 @@ export function timespan(
|
|||
queryTimespan,
|
||||
granularity
|
||||
};
|
||||
result[prefix + 'values-all'] = params.values;
|
||||
|
||||
const args = typeof format !== 'string' && format.args || {};
|
||||
let values = params[args.data || 'values'];
|
||||
|
||||
result[prefix + 'values-all'] = values;
|
||||
result[prefix + 'values-selected'] = state.selectedValue;
|
||||
|
||||
return result;
|
||||
|
|
|
@ -22,6 +22,8 @@ Connection plugins are connected to Data Source plugins. A Data Source can have
|
|||
|
||||
## Data Source Plugins
|
||||
|
||||
[How to create a Data Source Plugin](add-new-data-source.md)
|
||||
|
||||
* Constant
|
||||
* Sample
|
||||
* Application Insights
|
||||
|
@ -32,6 +34,8 @@ Connection plugins are connected to Data Source plugins. A Data Source can have
|
|||
|
||||
## Elements Plugins
|
||||
|
||||
[How to create a Visual Plugin](add-new-element.md)
|
||||
|
||||
* [Area Chart](components/area.md)
|
||||
* [Bar Chart](components/bar.md)
|
||||
* [Detail View](components/detail.md)
|
||||
|
@ -45,6 +49,8 @@ Connection plugins are connected to Data Source plugins. A Data Source can have
|
|||
|
||||
## data-formats plugins
|
||||
|
||||
[A short excerpt on data-formats](data-formats)
|
||||
|
||||
# Additional Features
|
||||
|
||||
* [Two Modes Elements](two-modes-element.md)
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
# Adding a new Data Source to Ibex Dashboard
|
||||
|
||||
There are three types of data sources existing in Ibex today:
|
||||
|
||||
1. Unchanging data source - The source for the data remains unchanging (while the state is updatable)
|
||||
2. Direct API calls - The data is received by querying a remote API
|
||||
3. Server API calls - The data is received by querting the server (This approach is used for APIs which SDKs require Node.js)
|
||||
|
||||
Let's see how to create a general data source that:
|
||||
* Uses an external API
|
||||
* That API requires a `username` and `password`
|
||||
* We can send a string query to that API
|
||||
* We can add parameters to that query
|
||||
|
||||
## Connection
|
||||
Connection types are used to store authentication information for the remote APIs.
|
||||
While a Connection is not required for unchanging data sources, defining a connection once, enable different data sources to query the same API with the same credentials.
|
||||
|
||||
Under `/client/src/data-sources/connections` create a new file like this (for the sake of this article, we'll use the name 'new-connection'):
|
||||
|
||||
`new-connection.tsx`:
|
||||
|
||||
```tsx
|
||||
import * as React from 'react';
|
||||
import { IConnection, ConnectionEditor, IConnectionProps } from './Connection';
|
||||
|
||||
/**
|
||||
* This class is used to define the parameters for the connection
|
||||
* as well as the editor for the connection
|
||||
*/
|
||||
export default class NewConnection implements IConnection {
|
||||
type = 'new-connection';
|
||||
params = [ 'username', 'password' ];
|
||||
editor = AzureConnectionEditor;
|
||||
}
|
||||
|
||||
/**
|
||||
* In case not all the parameters are filled up, or the user wants to edit the parameters
|
||||
* they can use this class to edit the those parameters
|
||||
*/
|
||||
class NewConnectionEditor extends ConnectionEditor<IConnectionProps, any> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>...</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Then, do to `/client/src/data-sources/connections/index.ts` and add your new connection type:
|
||||
|
||||
```ts
|
||||
import NewConnection from './new-connection';
|
||||
|
||||
var connectionTypes = [
|
||||
...,
|
||||
NewConnection ];
|
||||
|
||||
```
|
||||
|
||||
## Data Source Creation
|
||||
After creating the connection type, we need to define the data source and its options.
|
||||
|
||||
Under `/client/src/data-sources/plugins` create a new file like this (for the sake of this article, we'll use the name 'new-data-source'):
|
||||
|
||||
`new-data-source.ts`:
|
||||
|
||||
```ts
|
||||
import * as request from 'xhr-request';
|
||||
import { DataSourcePlugin, IOptions } from './DataSourcePlugin';
|
||||
import { DataSourceConnector } from '../DataSourceConnector';
|
||||
import NewConnection from '../connections/new-connection';
|
||||
|
||||
let connectionType = new GraphQLConnection();
|
||||
|
||||
interface INewDataSourceParams {
|
||||
query: string;
|
||||
parameters: Object;
|
||||
}
|
||||
|
||||
export default class NewDataSource extends DataSourcePlugin<INewDataSourceParams> {
|
||||
type = 'NewDataSource'; // The name of the data source to be used in a template
|
||||
defaultProperty = 'data'; // The property holding the default value when adding this data source as a dependency
|
||||
connectionType = connectionType.type;
|
||||
|
||||
constructor(options: IOptions<INewDataSourceParams>, connections: IDict<IStringDictionary>) {
|
||||
super(options, connections);
|
||||
|
||||
// this._props.params is of type INewDataSourceParams, and will hold the requested pararms
|
||||
this.validateParams(this._props.params);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called each time the dependencies for this data source are updated
|
||||
*/
|
||||
dependenciesUpdated(dependencies: any) {
|
||||
|
||||
// Ensure dependencies exist
|
||||
const isAnyDependencyMissing = Object.keys(this.getDependencies()).some(key => dependencies[key] == null);
|
||||
if (isAnyDependencyMissing) {
|
||||
return dispatch => dispatch();
|
||||
}
|
||||
|
||||
// Validate connection
|
||||
const connection = this.getConnection();
|
||||
const { username, password } = connection;
|
||||
if (!connection || !username || !password) {
|
||||
return dispatch => dispatch();
|
||||
}
|
||||
|
||||
const params = this.getParams() || ({} as INewDataSourceParams);
|
||||
const query = params.query || '';
|
||||
const parameters = dependencies['parameters'] || params.variables;
|
||||
|
||||
return dispatch => {
|
||||
request('https://reqres.in/api/users', {
|
||||
method: 'GET',
|
||||
json: true,
|
||||
/**
|
||||
* Please notice, in this sample, this API required no username/password/query/params
|
||||
*/
|
||||
body: {
|
||||
username: username,
|
||||
password: password,
|
||||
query: query,
|
||||
parameters: parameters
|
||||
}
|
||||
}, (err, json) => {
|
||||
|
||||
if (err) {
|
||||
return this.failure(err);
|
||||
}
|
||||
|
||||
// Using https://reqres.in/api/users
|
||||
// json contains {data: [...], total: 6, etc... }
|
||||
return dispatch(json);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used mainly by filters to support updating selected values by the user.
|
||||
* Unless you have to change it, copy it as is.
|
||||
*/
|
||||
updateSelectedValues(dependencies: IDictionary, selectedValues: any) {
|
||||
if (Array.isArray(selectedValues)) {
|
||||
return Object.assign(dependencies, { 'selectedValues': selectedValues });
|
||||
} else {
|
||||
return Object.assign(dependencies, { ... selectedValues });
|
||||
}
|
||||
}
|
||||
|
||||
private validateParams(params: INewDataSourceParams): void {
|
||||
if (true) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('Params are bad');
|
||||
}
|
||||
}
|
||||
```
|
||||
Then, to declare this data source, edit the following file:
|
||||
|
||||
`/client/src/data-sources/plugins/index.ts`
|
||||
|
||||
```ts
|
||||
import NewDataSource from './NewDataSource';
|
||||
|
||||
export default {
|
||||
...,
|
||||
'NewDataSource': NewDataSource
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Using in a template
|
||||
|
||||
To use the data source in a dashboard:
|
||||
|
||||
`example.private.js`:
|
||||
|
||||
```js
|
||||
return {
|
||||
id: "example",
|
||||
...,
|
||||
config: {
|
||||
connections: {
|
||||
/* The following parameters will be automatically requested if not supplied here */
|
||||
/* "new-connection" is derived from the type property of the Connection Type class */
|
||||
"new-connection": { username: "admin",password: "123456" }
|
||||
},
|
||||
layout: {...}
|
||||
},
|
||||
dataSources: [
|
||||
{
|
||||
id: "some_id",
|
||||
type: "NewDataSource",
|
||||
params: {
|
||||
query: "SELECT * FROM USERS",
|
||||
parameters: { a: 1, b: 2, c: 3 }
|
||||
},
|
||||
format: { type: "bars", valueField: "id", barsField: "first_name", seriesField: "lastName" }
|
||||
}
|
||||
],
|
||||
elements: [
|
||||
{
|
||||
id: "bar_sample1",
|
||||
type: "BarData",
|
||||
title: "Some Bar Data",
|
||||
subtitle: "Description of bar sample 1",
|
||||
size: { w: 5, h: 8 },
|
||||
source: "some_id",
|
||||
}
|
||||
],
|
||||
...
|
||||
}
|
||||
```
|
|
@ -0,0 +1,132 @@
|
|||
# Adding a Visual Element
|
||||
|
||||
The current project holds a collection of visual plugin is.
|
||||
For the full list [click here](README.md#elements-plugins).
|
||||
|
||||
To create a new visual component we will need to things:
|
||||
1. Create a new `GenericComponent` class that will display the component.
|
||||
2. Create a new `data-format` to transform the data to fit this visual component.
|
||||
|
||||
To read more about data-formats [click here](data-formats).
|
||||
|
||||
This article explains how to create a generic component that:
|
||||
* Displays an array of data in a `ul` list.
|
||||
* Enables defining the color of the text in the `ul`.
|
||||
|
||||
## Creating a new Visual Component
|
||||
|
||||
Under `/client/src/components/generic` create a new class (for the sake of this article we will use NewComponent):
|
||||
|
||||
`NewComponent.tsx`:
|
||||
|
||||
```tsx
|
||||
import * as React from 'react';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import Card from '../../Card';
|
||||
import { GenericComponent, IGenericProps, IGenericState } from '../GenericComponent';
|
||||
|
||||
interface INewProps extends IGenericProps {
|
||||
props: {
|
||||
textColor: string;
|
||||
};
|
||||
};
|
||||
|
||||
interface INewState extends IGenericState {
|
||||
values: any[];
|
||||
}
|
||||
|
||||
export default class BarData extends GenericComponent<INewProps, INewState> {
|
||||
|
||||
/**
|
||||
* This method is used to translate data received from a data-format
|
||||
* It means, it will look for 'values' property in a source of data.
|
||||
*/
|
||||
static fromSource(source: string) {
|
||||
return {
|
||||
values: GenericComponent.sourceFormat(source, 'values'),
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
values: []
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
let { values } = this.state;
|
||||
let { id, title, subtitle, props } = this.props;
|
||||
let { textColor } = props;
|
||||
|
||||
let liElements = [];
|
||||
if (!values || !values.length) {
|
||||
liElements = [
|
||||
(
|
||||
<li><div style={{ padding: 20 }}>No data is available</div></li>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
else {
|
||||
liElements = values.map((value, idx) => {
|
||||
return (
|
||||
<li key={idx} style={{ color: textColor }} >
|
||||
{JSON.stringify(value)}
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Todo: Receive the width of the SVG component from the container
|
||||
return (
|
||||
<Card id={id} title={title} subtitle={subtitle}>
|
||||
<ul>
|
||||
{liElements}
|
||||
</ul>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Creating the data-format
|
||||
|
||||
To create the correlative data format, we need to create a data transformer the outputs a 'values' property (To correlate to the property in the `fromSource` static method).
|
||||
|
||||
We'll create a data format that receives an array and stores it in a format attirbute named `values` in correlation to the requested `values` in the `fromSource` static method.
|
||||
|
||||
Under `/client/src/utils/data-formats/formats` create the following file (we'll use 'new-format' for the sake of this article):
|
||||
|
||||
`new-format.ts`:
|
||||
|
||||
```ts
|
||||
import { DataFormatTypes, IDataFormat, formatWarn, getPrefix } from '../common';
|
||||
import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugin';
|
||||
|
||||
/**
|
||||
* Please look at other data formats, to follow documentation formats
|
||||
*/
|
||||
export function filter (
|
||||
format: string | IDataFormat,
|
||||
state: any,
|
||||
dependencies: IDictionary,
|
||||
plugin: IDataSourcePlugin,
|
||||
prevState: any) {
|
||||
|
||||
const args = typeof format !== 'string' ? format.args : {};
|
||||
const prefix = getPrefix(format);
|
||||
const data = args.data || 'values'; // This property is used to look for the data location
|
||||
const field = args.field || 'value';
|
||||
const unknown = args.unknown || 'unknown';
|
||||
|
||||
const values = state[data];
|
||||
if (!values) { return null; }
|
||||
|
||||
let result = {};
|
||||
result[prefix + 'values'] = values;
|
||||
return result;
|
||||
}
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
# Data Formats
|
||||
Data formats are data transformers that align the data received from a data source to the format required by a visual component.
|
||||
|
||||
The available data formats are:
|
||||
|
||||
* [bars](../../client/src/utils/data-formats/formats/bars.ts)
|
||||
* [filter](../../client/src/utils/data-formats/formats/filter.ts)
|
||||
* [filtered-samples](../../client/src/utils/data-formats/formats/filtered-samples.ts)
|
||||
* [flags](../../client/src/utils/data-formats/formats/flags.ts)
|
||||
* [retention](../../client/src/utils/data-formats/formats/retention.ts)
|
||||
* [scorecard](../../client/src/utils/data-formats/formats/scorecard.ts)
|
||||
* [timeline](../../client/src/utils/data-formats/formats/timeline.ts)
|
||||
* [timespan](../../client/src/utils/data-formats/formats/timespan.ts)
|
||||
|
||||
To create a new Visual Element with a correlating data-format [click here](../add-new-element.md).
|
Загрузка…
Ссылка в новой задаче