feature: enable popup with composed model info (#460)

* feature: enable popup

* refactor

* fix: on when original model was deleted/not found.

* fix: no info message

* fix: subsitute brackets to info emoji
This commit is contained in:
alex-krasn 2020-08-05 08:03:11 -07:00 коммит произвёл GitHub
Родитель f4d53cec96
Коммит c1f5d803f0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 186 добавлений и 66 удалений

Просмотреть файл

@ -1,5 +1,27 @@
# Test Runbook
## **Feat: add composedNames popup for each model**
> ### Feature description ###
- On compose model page add popup for composed models to show which models been used for the composition of this model
> ### Use Case ###
**`I want`** I want to know the models been used to compose a model
**`So`** I can double click that model to invoke the pop up and checkout models been used
**`Given`** I've opened a project containing documents and I'm on the Model Compose page
**`When`** I double click a row with composed model
**`Then`** I should see a pop up, which it shows all models we used to compose in the list. Beside, there is also a filter field in the top to filter a specific model out of the list
> ### Acceptance criteria ###
#### Scenario One ####
**`Given`** I've opened a project containing documents and I'm on the Model Compose page
**`When`** I double click a row with composed model
**`Then`** I should see a pop up, which it shows all models we used to compose in the list. Beside, there is also a filter field in the top to filter a specific model out of the list
## Feat: support group selection tool
> ### Feature description ###

Просмотреть файл

@ -182,7 +182,8 @@ export const english: IAppStrings = {
},
errors: {
failedCompose: "Something went wrong composed model was not created!"
failedCompose: "Something went wrong composed model was not created!",
noInfoAboutModel: " Original model not found. No information available.",
}
},
predict: {

Просмотреть файл

@ -182,7 +182,8 @@ export const spanish: IAppStrings = {
checkAllButtonAria: "Botón de verificación Seleccionar todos los modelos",
},
errors: {
failedCompose: "¡Algo salió mal, el modelo compuesto no fue creado!"
failedCompose: "¡Algo salió mal, el modelo compuesto no fue creado!",
noInfoAboutModel: " Modelo original no encontrado. No hay información disponible.",
}
},
predict: {

Просмотреть файл

@ -182,6 +182,7 @@ export interface IAppStrings {
},
errors: {
failedCompose: string,
noInfoAboutModel: string,
}
}
predict: {

Просмотреть файл

@ -7,6 +7,7 @@ import { getDarkGreyTheme, getPrimaryGreenTheme, getPrimaryGreyTheme } from "../
import { strings } from "../../../../common/strings";
import { IModel } from "./modelCompose";
import { getAppInsights } from '../../../../services/telemetryService';
import "./modelCompose.scss";
export interface IComposeModelViewProps {
@ -17,6 +18,7 @@ export interface IComposeModelViewState {
hideModal: boolean;
items: IModel[];
cannotBeIncludeModels: IModel[];
composing: boolean;
}
export default class ComposeModelView extends React.Component<IComposeModelViewProps, IComposeModelViewState> {
@ -32,6 +34,7 @@ export default class ComposeModelView extends React.Component<IComposeModelViewP
hideModal: true,
items: [],
cannotBeIncludeModels: [],
composing: false,
}
}
@ -77,12 +80,40 @@ export default class ComposeModelView extends React.Component<IComposeModelViewP
}
];
const modelDetailsColumns: IColumn[] = [
{
key: "column2",
name: strings.modelCompose.column.id.headerName,
minWidth: 150,
maxWidth: 250,
isResizable: true,
onRender: (model) => <span>{model.id}</span>,
},
{
key: "column3",
name: strings.modelCompose.column.name.headerName,
minWidth: 100,
maxWidth: 330,
isResizable: true,
onRender: (model) => <span>{model.name}</span>,
},
{
key: "column4",
name: strings.modelCompose.column.created.headerName,
minWidth: 100,
maxWidth: 250,
isResizable: true,
onRender: (model) => <span>{model.createdDateTime ? new Date(model.createdDateTime).toLocaleString() : "N/A"}</span>,
}
];
const dark: ICustomizations = {
settings: {
theme: getDarkGreyTheme(),
},
scopedSettings: {},
};
return (
<Customizer {...dark}>
<Modal
@ -91,79 +122,99 @@ export default class ComposeModelView extends React.Component<IComposeModelViewP
isModeless={false}
containerClassName="modal-container"
scrollableContentClassName="scrollable-content"
>
<h4>Add name for composed model</h4>
<TextField
className="modal-textfield"
placeholder={strings.modelCompose.modelView.addComposeModelName}
onChange={this.onTextChange}
/>
>
{
this.state.items &&
<DetailsList
className="modal-list-container"
items={this.state.items}
columns={columns}
compact={true}
setKey="none"
selectionMode={SelectionMode.none}
isHeaderVisible={true}
layoutMode={DetailsListLayoutMode.justified}
/>
this.state.composing && <>
<h4>Add name for composed model</h4>
<TextField
className="modal-textfield"
placeholder={strings.modelCompose.modelView.addComposeModelName}
onChange={this.onTextChange}
/>
{
this.state.items &&
<DetailsList
className="modal-list-container"
items={this.state.items}
columns={columns}
compact={true}
setKey="none"
selectionMode={SelectionMode.none}
isHeaderVisible={true}
layoutMode={DetailsListLayoutMode.justified}
/>
}
{
this.state.cannotBeIncludeModels.length > 0 &&
<div className="excluded-items-container">
<h6>{this.state.cannotBeIncludeModels.length > 1 ? strings.modelCompose.modelView.modelsCannotBeIncluded : strings.modelCompose.modelView.modelCannotBeIncluded}</h6>
<DetailsList
className="excluded-items-list"
items={this.state.cannotBeIncludeModels}
columns={columns}
compact={true}
setKey="none"
selectionMode={SelectionMode.none}
isHeaderVisible={true}
layoutMode={DetailsListLayoutMode.justified}
/>
</div>
}
{
this.state.items.length < 2 &&
<div className="modal-alert">{strings.modelCompose.modelView.NotEnoughModels}</div>
}
<div className="modal-buttons-container">
<PrimaryButton
className="model-confirm"
theme={getPrimaryGreenTheme()}
onClick={this.confirm}>
Compose
</PrimaryButton>
<PrimaryButton
className="modal-cancel"
theme={getPrimaryGreyTheme()}
onClick={this.close}
>Close</PrimaryButton>
</div>
</>
}
{
this.state.cannotBeIncludeModels.length > 0 &&
<div className="excluded-items-container">
<h6>{this.state.cannotBeIncludeModels.length > 1 ? strings.modelCompose.modelView.modelsCannotBeIncluded : strings.modelCompose.modelView.modelCannotBeIncluded}</h6>
!this.state.composing &&
<>
<h5 style={{whiteSpace: 'pre'}}>{"Model " + this.state.items["modelId"] + " " + (this.state.items["modelName"] ? `(${this.state.items["modelName"]})` : "") + "\ncreated on " + new Date(this.state.items["createdDateTime"]).toLocaleString() + "\nincludes following models:"}</h5>
<DetailsList
className="excluded-items-list"
items={this.state.cannotBeIncludeModels}
columns={columns}
items={this.state.items["composedTrainResults"]}
columns={modelDetailsColumns}
compact={true}
setKey="none"
selectionMode={SelectionMode.none}
isHeaderVisible={false}
isHeaderVisible={true}
layoutMode={DetailsListLayoutMode.justified}
/>
</div>
}
{
this.state.items.length < 2 &&
<div className="modal-alert">{strings.modelCompose.modelView.NotEnoughModels}</div>
}
<div className="modal-buttons-container">
<PrimaryButton
className="model-confirm"
theme={getPrimaryGreenTheme()}
onClick={this.confirm}>
Compose
</PrimaryButton>
<PrimaryButton
className="modal-cancel"
theme={getPrimaryGreyTheme()}
onClick={this.close}
>
Close
</PrimaryButton>
</div>
<div className="modal-buttons-container">
<PrimaryButton
className="modal-cancel"
theme={getPrimaryGreyTheme()}
onClick={this.close}
>Close</PrimaryButton>
</div>
</>}
</Modal>
</Customizer>
)
}
public open = (models: any, cannotBeIncludeModels: any) => {
public open = (models: any, cannotBeIncludeModels: any, composing: boolean) => {
this.setState({
hideModal: false,
items: models,
cannotBeIncludeModels,
composing
})
}
public close = () => {
this.setState({
hideModal: true,
})
}
public close = () => this.setState({ hideModal: true });
public confirm = () => {
if (this.state.items.length > 1) {

Просмотреть файл

@ -68,15 +68,21 @@ export interface IModelComposePageState {
}
export interface IModel {
key: string;
modelId: string;
modelName: string;
createdDateTime: string;
lastUpdatedDateTime: string;
status: string;
attributes?: {
isComposed: boolean;
};
key?: string;
modelId: string;
modelName: string;
createdDateTime: string;
lastUpdatedDateTime?: string;
status?: string;
composedTrainResults?: [];
}
export interface IComposedModelInfo {
id: string ,
name?: string,
createdDateTime?: string;
}
function mapStateToProps(state: IApplicationState) {
@ -303,6 +309,7 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
isHeaderVisible={true}
selection={this.selection}
selectionPreservedOnEmptyClick={true}
onItemInvoked={this.onItemInvoked}
onRenderDetailsHeader={onRenderDetailsHeader}
onRenderRow={this.onRenderRow}>
</DetailsList>
@ -333,9 +340,9 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
</ScrollablePane>
</div>
<ComposeModelView
ref={this.composeModalRef}
onComposeConfirm={this.onComposeConfirm}
/>
ref={this.composeModalRef}
onComposeConfirm={this.onComposeConfirm}
/>
</Customizer>
</Fabric>
<PreventLeaving
@ -349,6 +356,42 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
return item.key;
}
private onItemInvoked = async (model: IModel, index: number, ev: Event) => {
const composedModelInfo: IModel = {
modelId: model.modelId,
modelName: model.modelName,
createdDateTime: model.createdDateTime,
composedTrainResults: []
};
if (model.attributes.isComposed) {
const inclModels = model.composedTrainResults ?
model.composedTrainResults
: (await this.getModelByURl(constants.apiModelsPath + "/" + model.modelId)).composedTrainResults;
for (const i of Object.keys(inclModels)) {
let _model: IModel;
let modelInfo: IComposedModelInfo;
try {
_model = await this.getModelByURl(constants.apiModelsPath + "/" + inclModels[i].modelId);
modelInfo = {
id: _model.modelId,
name: _model.modelName,
createdDateTime: _model.createdDateTime,
};
composedModelInfo.composedTrainResults.push(modelInfo as never);
} catch (e) {
modelInfo = {
id: inclModels[i].modelId,
name: strings.modelCompose.errors.noInfoAboutModel,
};
composedModelInfo.composedTrainResults.push(modelInfo as never);
}
}
this.composeModalRef.current.open(composedModelInfo, false, false);
}
}
private returnReadyModels = (modelList) => modelList.filter((model: IModel) => model.status === constants.statusCodeReady);
private getModelList = async () => {
@ -425,6 +468,7 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
const res = await this.getResponse(idURL);
const model: IModel = res.data.modelInfo;
model.key = model.modelId;
model.composedTrainResults = res.data.composedTrainResults;
return model;
}
@ -582,7 +626,7 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
}
private onComposeButtonClick = () => {
this.composeModalRef.current.open(this.selectedItems, this.cannotBeIncludedItems);
this.composeModalRef.current.open(this.selectedItems, this.cannotBeIncludedItems, true);
}
private onComposeConfirm = (composeModelName: string) => {
@ -599,8 +643,8 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
}
/**
* Poll function to repeatly check if request succeeded
* @param func - function that will be called repeatly
* Poll function to repeatedly check if request succeeded
* @param func - function that will be called repeatedly
* @param timeout - timeout
* @param interval - interval
*/