Commit b3029516 authored by Matija Obreza's avatar Matija Obreza

Admin KPI: UI using ApiCall and updated state management

parent 802a0012
......@@ -1732,6 +1732,12 @@
"name": "execution",
"title": "Execution info"
},
"execution": {
"onDelete": {
"message": "Delete execution {{name}}?",
"description": "This will remove the execution {{name}} and all related data."
}
},
"executionDisplay": {
"lastRun": "Last run ",
"runFrom": "Run from ",
......
......@@ -75,59 +75,45 @@ export default function admin(state = INITIAL_STATE, action: IReducerAction) {
}
case ADMIN_RECEIVE_EXEC: {
const {apiCall} = action.payload;
console.log(`ADMIN_RECEIVE_EXEC`, apiCall);
if (!state.exec.page || !state.exec.page.data.content) {
return update(state, {
exec: {
details: {
const updates: any = { exec: {} };
if (state.exec.details) {
if (apiCall.data) {
if (state.exec.details.data && apiCall.data.id === state.exec.details.data.execution.id) {
updates.exec.details = {
data: { execution: { $set: apiCall.data } },
};
} else {
updates.exec.details = {
data: {
execution: {$set: apiCall.data},
execution: { $set: apiCall.data },
runs: { $set: null },
lastRun: { $set: null },
},
loading: {$set: apiCall.loading},
error: {$set: apiCall.error},
},
},
});
};
}
}
}
const receivedIndex = state.exec.page && state.exec.page.data && apiCall.data && state.exec.page.data.content.findIndex((item) => item.name === apiCall.data.name);
if (receivedIndex !== -1) {
return update(state, {
exec: {
page: {
data: {
content: {
[receivedIndex]: {$set: apiCall.data},
},
},
},
details: {
data: {
execution: {$set: apiCall.data},
},
loading: {$set: apiCall.loading},
error: {$set: apiCall.error},
},
},
});
} else {
return !apiCall.data ? state : update(state, {
exec: {
page: {
data: {
content: {$push: [apiCall.data]},
},
if (apiCall.data && state.exec.page && state.exec.page.data) {
// update page if exists
const receivedIndex = apiCall.data ? state.exec.page.data.content.findIndex((item) => item.id === apiCall.data.id) : -1;
if (receivedIndex !== -1) {
updates.exec.page = {
data: {
content: { [receivedIndex]: { $set: apiCall.data } },
},
details: {
data: {
execution: {$set: apiCall.data},
},
loading: {$set: apiCall.loading},
error: {$set: apiCall.error},
};
} else {
updates.exec.page = {
data: {
content: {$push: [apiCall.data]},
},
},
});
};
}
}
return update(state, updates);
}
case ADMIN_REMOVE_EXEC: {
......@@ -201,34 +187,31 @@ export default function admin(state = INITIAL_STATE, action: IReducerAction) {
}
case ADMIN_RECEIVE_DIM: {
const {apiCall} = action.payload;
const receivedIndex = state.dim.page && state.dim.page.data && apiCall.data ? state.dim.page.data.content.findIndex((item) => item.name === apiCall.data.name) : -1;
if (receivedIndex !== -1) {
return update(state, {
dim: {
page: {
data: {
content: {
[receivedIndex]: {$set: apiCall.data},
},
},
const updates: any = {
dim: {
details: {$set: apiCall},
},
};
if (apiCall.data && state.dim.page && state.dim.page.data) {
const receivedIndex = apiCall.data ? state.dim.page.data.content.findIndex((item) => item.id === apiCall.data.id) : -1;
if (receivedIndex !== -1) {
updates.dim.page = {
data: {
content: { [receivedIndex]: { $set: apiCall.data } },
},
details: {$set: apiCall},
},
});
} else {
return !apiCall.data ? state : update(state, {
dim: {
page: {
data: {
content: {$push: [apiCall.data]},
},
};
} else {
updates.dim.page = {
data: {
content: {$push: [apiCall.data]},
},
details: {$set: apiCall},
},
});
};
}
}
return update(state, updates);
}
case ADMIN_REMOVE_DIM: {
const {apiCall} = action.payload;
const toDeleteIndex = state.dim && state.dim.page && state.dim.page.data && apiCall.data && state.dim.page.data.content ? state.dim.page.data.content.findIndex((item) => apiCall.data.name === item.name) : -1;
......@@ -262,33 +245,31 @@ export default function admin(state = INITIAL_STATE, action: IReducerAction) {
case ADMIN_RECEIVE_PARAM: {
const {apiCall} = action.payload;
const receivedIndex = apiCall.data && state.param.page && state.param.page.data ? state.param.page.data.content.findIndex((item) => item.name === apiCall.data.name) : -1;
const updates: any = {
param: {
details: {$set: apiCall},
},
};
if (receivedIndex !== -1) {
return update(state, {
param: {
page: {
data: {
content: {
[receivedIndex]: {$set: apiCall.data},
},
},
// console.log(`Received param update`, apiCall);
if (apiCall.data && state.param.page && state.param.page.data) {
const receivedIndex = apiCall.data ? state.param.page.data.content.findIndex((item) => item.id === apiCall.data.id) : -1;
if (receivedIndex !== -1) {
updates.param.page = {
data: {
content: { [receivedIndex]: { $set: apiCall.data } },
},
details: {$set: apiCall},
},
});
} else {
return !apiCall.data ? state : update(state, {
param: {
page: {
data: {
content: {$push: [apiCall.data]},
},
};
} else {
updates.param.page = {
data: {
content: {$push: [apiCall.data]},
},
details: {$set: apiCall},
},
});
};
}
}
return update(state, updates);
}
case ADMIN_REMOVE_PARAM: {
const {apiCall} = action.payload;
......
......@@ -106,6 +106,12 @@
"name": "execution",
"title": "Execution info"
},
"execution": {
"onDelete": {
"message": "Delete execution {{name}}?",
"description": "This will remove the execution {{name}} and all related data."
}
},
"executionDisplay": {
"lastRun": "Last run ",
"runFrom": "Run from ",
......
......@@ -39,21 +39,22 @@ import CreateNewButton from 'ui/common/buttons/CreateNewButton';
import ParameterIcon from '@material-ui/icons/Code';
import DimensionIcon from '@material-ui/icons/ShowChart';
import ExecutionIcon from '@material-ui/icons/ListAlt';
import ApiCall from 'model/ApiCall';
interface IDashboardProps extends React.ClassAttributes<any> {
classes: any;
t?: any;
execs: Page<Execution>;
execsCall: ApiCall<Page<Execution>>;
listExecutions: any;
toggleExecutionModal: (open: boolean) => void;
clearParameterDetails: () => void;
toggleParameterModal: (open: boolean) => void;
params: Page<KPIParameter>;
paramsCall: ApiCall<Page<KPIParameter>>;
listParameters: any;
clearDimDetails: () => void;
toggleDimensionModal: (open: boolean) => void;
dims: Page<Dimension<any>>;
dimsCall: ApiCall<Page<Dimension<any>>>;
listDimensions: any;
}
......@@ -66,16 +67,16 @@ class Dashboard extends React.Component<IDashboardProps, any> {
];
public componentWillMount() {
const { execs, listExecutions, params, listParameters, dims, listDimensions } = this.props;
console.log(`cwm: `, execs, params, dims);
if (! execs || !execs.content) {
const { execsCall, listExecutions, paramsCall, listParameters, dimsCall, listDimensions } = this.props;
console.log(`cwm: `, execsCall, paramsCall, dimsCall);
if (! execsCall || !execsCall.data) {
console.log(`Listing executions`);
listExecutions({});
}
if (! params) {
if (! paramsCall || !paramsCall.data) {
listParameters({});
}
if (! dims) {
if (! dimsCall || !dimsCall.data) {
listDimensions({});
}
}
......@@ -97,21 +98,19 @@ class Dashboard extends React.Component<IDashboardProps, any> {
}
public render() {
const { t, execs, params, dims } = this.props;
const stillLoading = ! execs && ! params && ! dims;
const { t, execsCall, paramsCall, dimsCall } = this.props;
const addActions = [
{
title: 'kpi.admin.p.parameterDialog.name',
action: () => this.openParameterDialog(),
icon: <ParameterIcon/>,
},
{
title: 'kpi.admin.p.dimensionDialog.name',
action: () => this.openDimensionDialog(),
icon: <DimensionIcon/>,
},
{
title: 'kpi.admin.p.parameterDialog.name',
action: () => this.openParameterDialog(),
icon: <ParameterIcon/>,
},
{
title: 'kpi.admin.p.executionDialog.name',
action: () => this.openExecutionDialog(),
......@@ -126,28 +125,29 @@ class Dashboard extends React.Component<IDashboardProps, any> {
<PageContents className="pt-1rem">
<Grid container spacing={ 0 }>
<Grid item xs={ 12 }>
{ stillLoading && <Loading /> }
{ (!execsCall || execsCall.loading) && <Loading /> }
<h5>{ t(`kpi.admin.p.dashboard.KPIExecutions`) }</h5>
{ execs && execs.content && execs.content.length > 0 &&
{ execsCall && execsCall.data && execsCall.data.content && execsCall.data.content.length > 0 &&
<div className="container-spacing-horizontal">
{ execs.content.map((exec, i) => (
{ execsCall.data.content.sort((a, b) => a.name.localeCompare(b.name)).map((exec, i) => (
<ExecutionCard key={ exec.id } index={ i } execution={ exec } compact/>
)) }
</div>
}
<h5>{ t(`kpi.admin.p.dashboard.KPIParameters`) }</h5>
{ params && params.content && params.content.length > 0 &&
{ (!paramsCall || paramsCall.loading) && <Loading /> }
{ paramsCall && paramsCall.data && paramsCall.data.content && paramsCall.data.content.length > 0 &&
<div className="container-spacing-horizontal">
{ params.content.map((param, i) => (
{ paramsCall.data.content.sort((a, b) => a.name.localeCompare(b.name)).map((param, i) => (
<ParameterCard key={ param.id } parameter={ param } index={ i } style={ { marginBottom: '8px' } }/>
)) }
</div>
}
<h5>{ t(`kpi.admin.p.dashboard.KPIDimensions`) }</h5>
{ dims && dims.content && dims.content.length > 0 &&
{ (!dimsCall || dimsCall.loading) && <Loading /> }
{ dimsCall && dimsCall.data && dimsCall.data.content && dimsCall.data.content.length > 0 &&
<div className="container-spacing-horizontal">
{ dims.content.map((dim, i) => (
{ dimsCall.data.content.sort((a, b) => a.name.localeCompare(b.name)).map((dim, i) => (
<DimensionCard key={ dim.id } dimension={ dim } index={ i } style={ { marginBottom: '8px' } }/>
)) }
</div>
......@@ -165,9 +165,9 @@ class Dashboard extends React.Component<IDashboardProps, any> {
}
const mapStateToProps = (state, ownProps) => ({
execs: state.kpi.admin.exec.page ? state.kpi.admin.exec.page.data : undefined,
dims: state.kpi.admin.dim.page ? state.kpi.admin.dim.page.data : undefined,
params: state.kpi.admin.param.page ? state.kpi.admin.param.page.data : undefined,
execsCall: state.kpi.admin.exec.page,
dimsCall: state.kpi.admin.dim.page,
paramsCall: state.kpi.admin.param.page,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
......
......@@ -57,7 +57,7 @@ class DimensionDialog extends React.Component<IDimensionDialogProps, any> {
<Dialog open={ isOpen } onClose={ this.hide } maxWidth="md" fullWidth disableEnforceFocus>
<DialogTitle>{ t(`kpi.admin.p.dimensionDialog.title`) }</DialogTitle>
<DialogContent>
<DimensionForm initialValues={ dimension } nameDisabled={ !!(dimension && dimension.name) } onCancel={ this.hide } onDelete={ dimension && this.handleDelete } onSubmit={ this.handleSubmit }/>
<DimensionForm initialValues={ dimension } onCancel={ this.hide } onDelete={ dimension && this.handleDelete } onSubmit={ this.handleSubmit }/>
</DialogContent>
</Dialog>
}
......@@ -65,6 +65,7 @@ class DimensionDialog extends React.Component<IDimensionDialogProps, any> {
);
}
}
// nameDisabled={ !!(dimension && dimension.name) }
const mapStateToProps = (state) => ({
isOpen: state.kpi.admin.dim.modalIsOpen,
......
......@@ -29,7 +29,7 @@ const styles = (theme) => ({
/*tslint:enable*/
interface IExecutionDialogProps extends React.ClassAttributes<any> {
saveExecution: (execution: Execution) => void;
saveExecution: (execution: Execution) => Promise<Execution>;
toggleExecutionModal: (open: boolean) => void;
execution: Execution;
buttonLabel?: string;
......@@ -52,8 +52,12 @@ class ExecutionDialog extends React.Component<IExecutionDialogProps, any> {
private handleSubmit = (execution: any) => {
const {saveExecution} = this.props;
execution.parameter = typeof execution.parameter === 'string' ? JSON.parse(execution.parameter) : execution.parameter;
saveExecution(execution);
this.hide();
saveExecution(execution).then((execution) => {
this.hide();
return execution;
}).catch((error) => {
console.log(error);
});
}
public render() {
......@@ -69,7 +73,7 @@ class ExecutionDialog extends React.Component<IExecutionDialogProps, any> {
<Dialog open={ isOpen } onClose={ this.hide } maxWidth="md" fullWidth>
<DialogTitle>{ t(`kpi.admin.p.executionDialog.title`) }</DialogTitle>
<DialogContent>
<ExecutionForm t={ t } nameDisabled={ !!(execution && execution.id !== null) } onCancel={ this.hide } onSubmit={ this.handleSubmit } initialValues={ execution }/>
<ExecutionForm t={ t } onCancel={ this.hide } onSubmit={ this.handleSubmit } initialValues={ execution }/>
</DialogContent>
</Dialog>
}
......@@ -77,6 +81,7 @@ class ExecutionDialog extends React.Component<IExecutionDialogProps, any> {
);
}
}
// nameDisabled={ !!(execution && execution.id !== null) }
const mapStateToProps = (state) => ({
isOpen: state.kpi.admin.exec.modalIsOpen,
......
......@@ -32,6 +32,8 @@ import Tabs, { Tab } from 'ui/common/Tabs';
import ChangesSection from 'kpi/ui/admin/c/ChangesSection';
import withStyles from '@material-ui/core/styles/withStyles';
import PageTitle from 'ui/common/PageTitle';
import ApiCall from 'model/ApiCall';
import confirm from 'utilities/confirmAlert';
// import { parse } from 'query-string';
......@@ -55,8 +57,8 @@ interface IExecutionProps extends React.ClassAttributes<any> {
loading: boolean;
loadMoreExecutionRuns: (name: string, page: number) => void;
loadExecutionChanges: any;
executionDetails: ExecutionDetails;
executionChanges: any;
executionDetailsCall: ApiCall<ExecutionDetails>;
executionChangesCall: ApiCall<any>;
getExecution: any;
executeExecution: any;
deleteExecution: any;
......@@ -74,15 +76,22 @@ class ExecutionDisplay extends React.Component<IExecutionProps, any> {
];
private delete = () => {
const {deleteExecution, executionDetails} = this.props;
console.log('Executing');
deleteExecution(executionDetails.execution);
const {t, deleteExecution, executionDetailsCall: { data: executionDetails }} = this.props;
confirm(t('kpi.admin.p.execution.onDelete.message', {name: executionDetails.execution.name}), {
description: t('kpi.admin.p.execution.onDelete.description'),
confirmLabel: t('common:action.delete'),
abortLabel: t('common:action.cancel'),
}).then(() => {
deleteExecution(executionDetails.execution);
}).catch(() => {
// don't delete
});
}
public componentWillMount() {
const { shortName, executionDetails, getExecution } = this.props;
console.log(`cwm: `, executionDetails);
if (!executionDetails || !executionDetails.execution || executionDetails.execution.name !== shortName) {
const { shortName, executionDetailsCall, getExecution } = this.props;
console.log(`cwm: `, executionDetailsCall);
if (!executionDetailsCall || (!executionDetailsCall.loading && (executionDetailsCall.data && executionDetailsCall.data.execution && executionDetailsCall.data.execution.name !== shortName))) {
console.log(`Loading execution ${shortName}`);
getExecution(shortName);
}
......@@ -97,14 +106,16 @@ class ExecutionDisplay extends React.Component<IExecutionProps, any> {
}
private execute = () => {
const { executeExecution, executionDetails } = this.props;
const { executeExecution, executionDetailsCall: { data: executionDetails }, loadMoreExecutionRuns } = this.props;
console.log('Executing');
executeExecution(executionDetails.execution.name);
executeExecution(executionDetails.execution.name).then(() => {
loadMoreExecutionRuns(executionDetails.execution.name, 0);
});
}
public componentWillReceiveProps(nextProps) {
const { shortName, executionDetails, getExecution } = nextProps;
if (!executionDetails || !executionDetails.execution || executionDetails.execution.name !== shortName) {
const { shortName, executionDetailsCall, getExecution } = nextProps;
if (!executionDetailsCall || (!executionDetailsCall.loading && (executionDetailsCall.data && executionDetailsCall.data.execution && executionDetailsCall.data.execution.name !== shortName))) {
console.log(`Reloading execution ${shortName}`);
getExecution(shortName);
}
......@@ -112,20 +123,23 @@ class ExecutionDisplay extends React.Component<IExecutionProps, any> {
public render() {
const {t, classes, loading, shortName, executionDetails, showSnackbar, loadMoreExecutionRuns, currentTab, loadExecutionChanges, executionChanges, loadExecutionRunByDate, loadExecutionRun} = this.props;
const execution = !executionDetails ? null : executionDetails.execution;
const {t, classes, shortName, executionDetailsCall, showSnackbar, loadMoreExecutionRuns, currentTab, loadExecutionChanges, executionChangesCall, loadExecutionRunByDate, loadExecutionRun} = this.props;
if (loading) {
if (! executionDetailsCall || executionDetailsCall.loading) {
console.log(`KPI Execution details loading...`);
return <Loading />;
}
const { data: executionDetails } = executionDetailsCall;
const execution = !executionDetails ? null : executionDetails.execution;
console.log(`KPI Execution details`, execution);
return (
<div>
<PageTitle title={ !loading && execution ? execution.title : t('common:label.loading', { what: t(`kpi.admin.p.executionDisplay.title`) }) }/>
<ContentHeaderWithButton title={ !loading && execution ? execution.title : t('common:label.loading', { what: t(`kpi.admin.p.executionDisplay.title`) }) } buttons={
<PageTitle title={ execution ? execution.title : t('common:label.loading', { what: t(`kpi.admin.p.executionDisplay.title`) }) }/>
<ContentHeaderWithButton title={ execution ? execution.title : t('common:label.loading', { what: t(`kpi.admin.p.executionDisplay.title`) }) } buttons={
<div>
<ExecutionDialog execution={ executionDetails && executionDetails.execution } buttonLabel={ t('common:action.edit') }/>
<ExecutionDialog execution={ execution } buttonLabel={ t('common:action.edit') }/>
<Button variant="contained" onClick={ this.delete }>{ t('common:action.delete') }</Button>
<Button variant="contained" onClick={ this.execute }>{ t('kpi.admin.p.executionDisplay.execute') }</Button>
{ executionDetails.execution._permissions.manage && <Permissions clazz={ Execution.clazz } id={ executionDetails.execution.id } variant="contained" /> }
......@@ -185,7 +199,7 @@ class ExecutionDisplay extends React.Component<IExecutionProps, any> {
}
>
{ currentTab === 'changes' ?
<ChangesSection changes={ executionChanges }
<ChangesSection changes={ executionChangesCall }
showSnackbar={ showSnackbar }
loadExecutionChanges={ loadExecutionChanges }
executionName={ execution.name }
......@@ -211,9 +225,8 @@ class ExecutionDisplay extends React.Component<IExecutionProps, any> {
}
const mapStateToProps = (state, ownProps) => ({
executionDetails: state.kpi.admin.exec.details ? state.kpi.admin.exec.details.data : undefined,
loading: state.kpi.admin.exec.details ? state.kpi.admin.exec.details.loading : true,
executionChanges: state.kpi.admin.exec.changes ? state.kpi.admin.exec.changes.data : undefined,
executionDetailsCall: state.kpi.admin.exec.details,
executionChangesCall: state.kpi.admin.exec.changes,
shortName: ownProps.match.params.shortName,
currentTab: ownProps.match.params.tab,
});
......
......@@ -15,9 +15,9 @@ import DialogTitle from '@material-ui/core/DialogTitle';
import ParameterForm from './c/parameterForm';
interface IParameterDialogProps extends React.ClassAttributes<any> {
saveParameter: (parameter: KPIParameter) => void;
saveParameter: (parameter: KPIParameter) => Promise<KPIParameter>;
clearParameterDetails: () => void;
deleteParameter: (parameter: KPIParameter) => void;
deleteParameter: (parameter: KPIParameter) => Promise<KPIParameter>;
toggleParameterModal: (open: boolean) => void;
parameter: KPIParameter;
t: any;
......@@ -31,15 +31,25 @@ class ParameterDialog extends React.Component<IParameterDialogProps, any> {
}
private handleSubmit = (parameter: KPIParameter) => {
const {saveParameter} = this.props;
saveParameter(parameter);
this.hide();
const { saveParameter, clearParameterDetails } = this.props;
saveParameter(parameter).then((parameter) => {
// console.log(parameter);
clearParameterDetails();
this.hide();
return parameter;
}).catch((error) => {
console.log(error);
});
}
private handleDelete = () => {
const {deleteParameter, parameter} = this.props;
deleteParameter(parameter);
this.hide();
deleteParameter(parameter).then(() => {
clearParameterDetails();
this.hide();
}).catch((error) => {
console.log(error);
});
}
public render() {
......@@ -50,7 +60,7 @@ class ParameterDialog extends React.Component<IParameterDialogProps, any> {
<Dialog open={ isOpen } onClose={ this.hide } maxWidth="md" fullWidth>
<DialogTitle>{ t(`kpi.admin.p.parameterDialog.title`) }</DialogTitle>
<DialogContent>
<ParameterForm initialValues={ parameter } nameDisabled={ !!(parameter && parameter.name) } onDelete={ parameter && this.handleDelete } onCancel={ this.hide } onSubmit={ this.handleSubmit }/>
<ParameterForm initialValues={ parameter } onDelete={ parameter && this.handleDelete } onCancel={ this.hide } onSubmit={