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:
Родитель
f4d53cec96
Коммит
c1f5d803f0
|
@ -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
|
||||
*/
|
||||
|
|
Загрузка…
Ссылка в новой задаче