Commit 6578a367 authored by Matija Obreza's avatar Matija Obreza

Initial code for KPI module

- Admin dashboard page lists params, dims and executions
- Show execution details with last run
parent 7fd76784
......@@ -54,6 +54,9 @@ module.exports = {
errors: true
},
clientLogLevel: "warning",
historyApiFallback: {
disableDotRule: true
},
proxy: {
'/proxy': {
target: API_URL,
......
import KpiService from 'service/genesys/KpiService';
import Page, { IPageRequest } from 'model/Page';
import KPIParameter from 'model/kpi/KPIParameter';
import { IReducerAction } from 'model/common.model';
import { ADMIN_RECEIVE_PARAMS, ADMIN_RECEIVE_DIMS, ADMIN_RECEIVE_EXECS, ADMIN_RECEIVE_EXEC, ADMIN_RECEIVE_EXEC_LASTRUN } from 'kpi/constants';
import Dimension from 'model/kpi/Dimension';
import Execution from 'model/kpi/Execution';
import ExecutionDetails from 'model/kpi/ExecutionDetails';
import ExecutionRun from 'model/kpi/ExecutionRun';
export const listExecutions = (page: IPageRequest) => (dispatch) => {
KpiService.listExecutions(page).then((data) => {
dispatch(receiveExecutions(data));
});
};
const receiveExecutions = (page: Page<Execution>): IReducerAction => ({
type: ADMIN_RECEIVE_EXECS, payload: page,
});
export const getExecution = (name: string) => (dispatch) => {
KpiService.executionDetails(name).then((data) => {
dispatch(receiveExecution(data));
});
};
const receiveExecution = (execution: ExecutionDetails): IReducerAction => ({
type: ADMIN_RECEIVE_EXEC, payload: execution,
});
export const executeExecution = (name: string) => (dispatch) => {
KpiService.runExecution(name).then((data) => {
dispatch(receiveExecutionRun(data));
});
};
const receiveExecutionRun = (executionRun: ExecutionRun): IReducerAction => ({
type: ADMIN_RECEIVE_EXEC_LASTRUN, payload: executionRun,
});
export const listDimensions = (page: IPageRequest) => (dispatch) => {
KpiService.listDimensions(page).then((data) => {
dispatch(receiveDimensions(data));
});
};
const receiveDimensions = (page: Page<Dimension<any>>): IReducerAction => ({
type: ADMIN_RECEIVE_DIMS, payload: page,
});
export const listParameters = (page: IPageRequest) => (dispatch) => {
KpiService.listParameters(page).then((data) => {
dispatch(receiveParameters(data));
});
};
const receiveParameters = (page: Page<KPIParameter>): IReducerAction => ({
type: ADMIN_RECEIVE_PARAMS, payload: page,
});
// export const RECEIVE_INSTITUTES = 'institutes/RECEIVE_INSTITUTES';
// export const APPEND_INSTITUTES = 'institutes/APPEND_INSTITUTES';
// export const RECEIVE_INSTITUTE = 'institutes/RECEIVE_INSTITUTE';
// export const DASHBOARD_RECEIVE_INSTITUTES = 'institutes/dashboard/RECEIVE_INSTITUTES';
// export const DASHBOARD_APPEND_INSTITUTES = 'institutes/dashboard/APPEND_INSTITUTES';
// export const DASHBOARD_RECEIVE_INSTITUTE = 'institutes/dashboard/RECEIVE_INSTITUTE';
// export const INSTITUTE_FILTERFORM = 'Form/institutes/INSTITUTE_FILTERFORM';
export const ADMIN_RECEIVE_EXECS = 'kpi/admin/RECEIVE_EXECS';
export const ADMIN_RECEIVE_EXEC = 'kpi/admin/RECEIVE_EXEC';
export const ADMIN_RECEIVE_PARAMS = 'kpi/admin/RECEIVE_PARAMS';
export const ADMIN_RECEIVE_DIMS = 'kpi/admin/RECEIVE_DIMS';
export const ADMIN_RECEIVE_EXEC_LASTRUN = 'kpi/admin/RECEIVE_LASTRUN';
import update from 'immutability-helper';
import {IReducerAction} from 'model/common.model';
import { ADMIN_RECEIVE_EXECS, ADMIN_RECEIVE_DIMS, ADMIN_RECEIVE_PARAMS, ADMIN_RECEIVE_EXEC, ADMIN_RECEIVE_EXEC_LASTRUN } from 'kpi/constants';
const INITIAL_STATE: {
} = {
exec: {
page: null,
details: null,
},
dim: {
page: null,
},
param: {
page: null,
},
};
export default function admin(state = INITIAL_STATE, action: IReducerAction) {
switch (action.type) {
case ADMIN_RECEIVE_EXECS: {
return update(state, {
exec: { page: { $set: action.payload } },
});
}
case ADMIN_RECEIVE_EXEC: {
return update(state, {
exec: { details: { $set: action.payload } },
});
}
case ADMIN_RECEIVE_EXEC_LASTRUN: {
// only update lastRun
return update(state, {
exec: { details: { lastRun: { $set: action.payload } } },
});
}
case ADMIN_RECEIVE_DIMS: {
return update(state, {
dim: { page: { $set: action.payload } },
});
}
case ADMIN_RECEIVE_PARAMS: {
return update(state, {
param: { page: { $set: action.payload } },
});
}
default:
return state;
}
}
// import update from 'immutability-helper';
const INITIAL_STATE: {
} = {
};
export default function dashboard(state = INITIAL_STATE, action: { type?: string, payload?: any } = {type: '', payload: {}}) {
switch (action.type) {
default:
return state;
}
}
import { combineReducers } from 'redux';
import admin from './admin';
// import dashboard from './dashboard';
// import kpi from './public';
const rootReducer = combineReducers({
admin,
// dashboard,
// public: kpi,
});
export default rootReducer;
// import update from 'immutability-helper';
const INITIAL_STATE: {
} = {
};
export default function kpiPublic(state = INITIAL_STATE, action: { type?: string, payload?: any } = {type: '', payload: {}}) {
switch (action.type) {
default:
return state;
}
}
import KpiAdminDashboard from 'kpi/ui/admin/Dashboard';
import ExecutionDisplay from 'kpi/ui/admin/ExecutionDisplay';
const publicRoutes = [
// not yet
];
const dashboardRoutes = [
// not yet
];
const adminRoutes = [
{
path: '/kpi/',
component: KpiAdminDashboard,
exact: true,
},
{
path: '/kpi/:shortName',
component: ExecutionDisplay,
exact: true,
},
];
export { publicRoutes, dashboardRoutes, adminRoutes };
import * as React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { translate } from 'react-i18next';
// import { parse } from 'query-string';
// Actions
import { listDimensions, listExecutions, listParameters } from 'kpi/actions/admin';
// Models
import Page from 'model/Page';
import Execution from 'model/kpi/Execution';
import Dimension from 'model/kpi/Dimension';
import KPIParameter from 'model/kpi/KPIParameter';
// UI
import { PageContents } from 'ui/layout/PageLayout';
import Loading from 'ui/common/Loading';
// import PagedLoader from 'ui/common/PagedLoader';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
interface IDashboardProps extends React.ClassAttributes<any> {
classes: any;
t?: any;
execs: Page<Execution>;
listExecutions: any;
params: Page<KPIParameter>;
listParameters: any;
dims: Page<Dimension<any>>;
listDimensions: any;
}
class Dashboard extends React.Component<IDashboardProps, any> {
protected static needs = [
];
public componentWillMount() {
const { execs, listExecutions, params, listParameters, dims, listDimensions } = this.props;
console.log(`cwm: `, execs, params, dims);
if (! execs) {
console.log(`Listing executions`);
listExecutions({});
}
if (! params) {
listParameters({});
}
if (! dims) {
listDimensions({});
}
}
private addParameter = () => {
console.log('Adding parameter');
}
private addDimension = () => {
console.log('Adding dim');
}
private addExecution = () => {
console.log('Adding exec');
}
public render() {
const { t, execs, params, dims } = this.props;
const stillLoading = ! execs && ! params && ! dims;
return (
<div>
<ContentHeaderWithButton title={ t(`p.admin.kpi.dashboard`) } buttons={ <div>
<Button onClick={ this.addParameter }>Parameter</Button>
<Button onClick={ this.addDimension }>Dimension</Button>
<Button onClick={ this.addExecution }>Execution</Button>
</div> }/>
<PageContents>
<Grid container spacing={ 0 }>
<Grid item xs={ 12 }>
{ stillLoading && <Loading /> }
<h5>KPI Executions</h5>
{ execs && execs.content.map((exec, i) => (
<div key={ exec.id }>
<Link to={ `/admin/kpi/${exec.name}` }><b>{ exec.title }</b></Link>
<div>{ exec.type } <code>{ exec.name }</code> { exec.property }</div>
<div>Looks at { exec.type } of { exec.property } of { exec.parameter.entity }</div>
</div>
)) }
<h5>KPI Parameters</h5>
{ params && params.content.map((param, i) => (
<div key={ param.id }>
<b>{ param.title }</b>
<div><code>{ param.name }</code> { param.entity } { param.condition }</div>
</div>
)) }
<h5>KPI Dimensions</h5>
{ dims && dims.content.map((dim, i) => (
<div key={ dim.id }>
<b>{ dim.title }</b>
<div><code>{ dim.name }</code></div>
</div>
)) }
</Grid>
</Grid>
</PageContents>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => ({
execs: state.kpi.admin.exec.page,
dims: state.kpi.admin.dim.page,
params: state.kpi.admin.param.page,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
listExecutions,
listParameters,
listDimensions,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)((translate()(Dashboard)));
import * as React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { translate } from 'react-i18next';
// import { parse } from 'query-string';
// Actions
import { getExecution, executeExecution } from 'kpi/actions/admin';
// Models
import ExecutionDetails from 'model/kpi/ExecutionDetails';
// UI
import { PageContents } from 'ui/layout/PageLayout';
import Loading from 'ui/common/Loading';
// import PagedLoader from 'ui/common/PagedLoader';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import Grid from '@material-ui/core/Grid';
import PrettyDate from 'ui/common/time/PrettyDate';
import Button from '@material-ui/core/Button';
import Number from 'ui/common/Number';
interface IExecutionProps extends React.ClassAttributes<any> {
classes: any;
t?: any;
shortName: string;
executionDetails: ExecutionDetails;
getExecution: any;
executeExecution: any;
}
class ExecutionDisplay extends React.Component<IExecutionProps, any> {
protected static needs = [
];
public componentWillMount() {
const { shortName, executionDetails, getExecution } = this.props;
console.log(`cwm: `, executionDetails);
if (! executionDetails || executionDetails.execution.name !== shortName) {
console.log(`Loading execution ${shortName}`);
getExecution(shortName);
}
}
public componentWillReceiveProps(nextProps) {
const { shortName, executionDetails, getExecution } = nextProps;
if (! executionDetails || executionDetails.execution.name !== shortName) {
console.log(`Reloading execution ${shortName}`);
getExecution(shortName);
}
}
private execute = () => {
const { executeExecution, executionDetails } = this.props;
console.log('Executing');
executeExecution(executionDetails.execution.name);
}
public render() {
const { t, shortName, executionDetails } = this.props;
const stillLoading = ! executionDetails || executionDetails.execution.name !== shortName;
const execution = executionDetails === null ? null : executionDetails.execution;
const runs = executionDetails === null ? null : executionDetails.runs;
const lastRun = executionDetails === null ? null : executionDetails.lastRun;
return (
<div>
<ContentHeaderWithButton title={ t(`p.admin.kpi.execution`) } buttons={ <div><Button onClick={ this.execute }>Execute</Button></div> } />
<PageContents>
<Grid container spacing={ 0 }>
<Grid item xs={ 12 }>
{ stillLoading && <Loading /> }
{ execution && (
<div key={ execution.id }>
<b>{ execution.title }</b>
<div>{ execution.type } <code>{ execution.name }</code> { execution.property }</div>
<div>Looks at { execution.type } of { execution.property } of { execution.parameter.entity }</div>
</div>
) }
<h5>Last run</h5>
<table>
{ lastRun && <thead>
<tr>
{ execution.executionDimensions.map((ed, i) => (
<td key={ ed.id }>
{ execution.parameter.entity } { ed.link } { ed.field }
</td>
)) }
<td>{ execution.type }</td>
{ execution.type === 'AVERAGE' && <td>Std. dev</td> }
</tr>
</thead> }
<tbody>
{ lastRun && lastRun.observations.sort((a, b) => b.value - a.value).map((obs, i) => (
<tr key={ obs.id }>
{ obs.dimensions.map((dim, i) => (
<td key={ i }>{ dim.value }</td>
)) }
<td><Number value={ obs.value } /></td>
{ obs.stdDev && <td><Number value={ obs.stdDev } /></td> }
</tr>
)) }
</tbody>
</table>
<h5>Runs</h5>
{ runs && runs.map((run, i) => (
<div key={ run.id }>
<PrettyDate value={ run.timestamp } />
</div>
)) }
</Grid>
</Grid>
</PageContents>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => ({
executionDetails: state.kpi.admin.exec.details,
shortName: ownProps.match.params.shortName,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
getExecution,
executeExecution,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)((translate()(ExecutionDisplay)));
import Dimension from 'model/kpi/Dimension';
export default class BooleanDimension extends Dimension<boolean> {
public mode: number;
}
/*
* Defined in Swagger as '#/definitions/Dimension«object»'
*/
class Dimension<T> {
public active: boolean;
public createdBy: number;
public createdDate: Date;
public id: number;
public lastModifiedBy: number;
public lastModifiedDate: Date;
public name: string;
public title: string;
public values: any[];
public version: number;
}
export default Dimension;
/*
* Defined in Swagger as '#/definitions/DimensionKey'
*/
class DimensionKey {
public id: number;
public name: string;
public value: string;
}
export default DimensionKey;
import ExecutionDimension from 'model/kpi/ExecutionDimension';
import KPIParameter from 'model/kpi/KPIParameter';
/*
* Defined in Swagger as '#/definitions/Execution'
*/
class Execution {
public active: boolean;
public createdBy: number;
public createdDate: Date;
public executionDimensions: ExecutionDimension[];
public id: number;
public lastModifiedBy: number;
public lastModifiedDate: Date;
public name: string;
public parameter: KPIParameter;
public property: string;
public title: string;
public type: string;
public version: number;
}
export default Execution;
import Execution from 'model/kpi/Execution';
import ExecutionRun from 'model/kpi/ExecutionRun';
/*
* Defined in Swagger as '#/definitions/ExecutionDetails'
*/
class ExecutionDetails {
public execution: Execution;
public runs: ExecutionRun[];
public lastRun: ExecutionRun;
}
export default ExecutionDetails;
import Dimension from 'model/kpi/Dimension';
/*
* Defined in Swagger as '#/definitions/ExecutionDimension'
*/
class ExecutionDimension {
public dimension: Dimension<any>;
public field: string;
public id: number;
public link: string;
}
export default ExecutionDimension;
import Observation from 'model/kpi/Observation';
/*
* Defined in Swagger as '#/definitions/ExecutionRun'
*/
class ExecutionRun {
public id: number;
public observations: Observation[];
public timestamp: Date;
}
export default ExecutionRun;