Added index refresh to SQL API indexing policy editor (#306)

* Index refresh component introduced

- Made all notifications in Mongo Index editor have 12 font size
- Added indexing policy refresh to sql indexing policy editor
- Added "you have unsaved changes" message, replace old message for lazy indexing policy changes

* formatting changes

* addressed PR comments
This commit is contained in:
Srinath Narayanan 2020-11-02 13:19:45 -08:00 committed by GitHub
parent 473f722dcc
commit e6ca1d25c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 249 additions and 156 deletions

View File

@ -0,0 +1,28 @@
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import * as Constants from "../Constants";
import { AuthType } from "../../AuthType";
export async function getIndexTransformationProgress(databaseId: string, collectionId: string): Promise<number> {
if (window.authType !== AuthType.AAD) {
return undefined;
}
let indexTransformationPercentage: number;
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
try {
const response = await client()
.database(databaseId)
.container(collectionId)
.read({ populateQuotaInfo: true });
indexTransformationPercentage = parseInt(
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
);
} catch (error) {
handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
throw error;
}
clearMessage();
return indexTransformationPercentage;
}

View File

@ -2,8 +2,6 @@ import { userContext } from "../../UserContext";
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources"; import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types"; import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import * as Constants from "../Constants";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
@ -30,29 +28,3 @@ export async function readMongoDBCollectionThroughRP(
clearMessage(); clearMessage();
return collection; return collection;
} }
export async function getMongoDBCollectionIndexTransformationProgress(
databaseId: string,
collectionId: string
): Promise<number> {
if (window.authType !== AuthType.AAD) {
return undefined;
}
let indexTransformationPercentage: number;
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
try {
const response = await client()
.database(databaseId)
.container(collectionId)
.read({ populateQuotaInfo: true });
indexTransformationPercentage = parseInt(
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
);
} catch (error) {
handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
throw error;
}
clearMessage();
return indexTransformationPercentage;
}

View File

@ -8,8 +8,8 @@ import * as DataModels from "../../../Contracts/DataModels";
import ko from "knockout"; import ko from "knockout";
import { TtlType, isDirty } from "./SettingsUtils"; import { TtlType, isDirty } from "./SettingsUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
jest.mock("../../../Common/dataAccess/readMongoDBCollection", () => ({ jest.mock("../../../Common/dataAccess/getIndexTransformationProgress", () => ({
getMongoDBCollectionIndexTransformationProgress: jest.fn().mockReturnValue(undefined) getIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
})); }));
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection"; import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
jest.mock("../../../Common/dataAccess/updateCollection", () => ({ jest.mock("../../../Common/dataAccess/updateCollection", () => ({

View File

@ -46,10 +46,8 @@ import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric
import "./SettingsComponent.less"; import "./SettingsComponent.less";
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent"; import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types"; import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
import { import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
getMongoDBCollectionIndexTransformationProgress, import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
readMongoDBCollectionThroughRP
} from "../../../Common/dataAccess/readMongoDBCollection";
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils"; import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
interface SettingsV2TabInfo { interface SettingsV2TabInfo {
@ -212,6 +210,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
} }
componentDidMount(): void { componentDidMount(): void {
this.refreshIndexTransformationProgress();
this.loadMongoIndexes(); this.loadMongoIndexes();
this.setAutoPilotStates(); this.setAutoPilotStates();
this.setBaseline(); this.setBaseline();
@ -233,8 +232,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.container.isEnableMongoCapabilityPresent() && this.container.isEnableMongoCapabilityPresent() &&
this.container.databaseAccount() this.container.databaseAccount()
) { ) {
await this.refreshIndexTransformationProgress();
this.mongoDBCollectionResource = await readMongoDBCollectionThroughRP( this.mongoDBCollectionResource = await readMongoDBCollectionThroughRP(
this.collection.databaseId, this.collection.databaseId,
this.collection.id() this.collection.id()
@ -249,10 +246,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}; };
public refreshIndexTransformationProgress = async (): Promise<void> => { public refreshIndexTransformationProgress = async (): Promise<void> => {
const currentProgress = await getMongoDBCollectionIndexTransformationProgress( const currentProgress = await getIndexTransformationProgress(this.collection.databaseId, this.collection.id());
this.collection.databaseId,
this.collection.id()
);
this.setState({ indexTransformationProgress: currentProgress }); this.setState({ indexTransformationProgress: currentProgress });
}; };
@ -352,6 +346,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
break; break;
} }
const wasIndexingPolicyModified = this.state.isIndexingPolicyDirty;
newCollection.defaultTtl = defaultTtl; newCollection.defaultTtl = defaultTtl;
newCollection.indexingPolicy = this.state.indexingPolicyContent; newCollection.indexingPolicy = this.state.indexingPolicyContent;
@ -387,6 +382,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy); this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy); this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
this.collection.geospatialConfig(updatedCollection.geospatialConfig); this.collection.geospatialConfig(updatedCollection.geospatialConfig);
if (wasIndexingPolicyModified) {
await this.refreshIndexTransformationProgress();
}
this.setState({ this.setState({
isSubSettingsSaveable: false, isSubSettingsSaveable: false,
isSubSettingsDiscardable: false, isSubSettingsDiscardable: false,
@ -946,6 +946,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline, indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline,
onIndexingPolicyContentChange: this.onIndexingPolicyContentChange, onIndexingPolicyContentChange: this.onIndexingPolicyContentChange,
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage, logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
indexTransformationProgress: this.state.indexTransformationProgress,
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
}; };

View File

@ -6,7 +6,7 @@ import {
getEstimatedAutoscaleSpendElement, getEstimatedAutoscaleSpendElement,
manualToAutoscaleDisclaimerElement, manualToAutoscaleDisclaimerElement,
ttlWarning, ttlWarning,
indexingPolicyTTLWarningMessage, indexingPolicynUnsavedWarningMessage,
updateThroughputBeyondLimitWarningMessage, updateThroughputBeyondLimitWarningMessage,
updateThroughputDelayedApplyWarningMessage, updateThroughputDelayedApplyWarningMessage,
getThroughputApplyDelayedMessage, getThroughputApplyDelayedMessage,
@ -37,7 +37,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
{manualToAutoscaleDisclaimerElement} {manualToAutoscaleDisclaimerElement}
{ttlWarning} {ttlWarning}
{indexingPolicyTTLWarningMessage} {indexingPolicynUnsavedWarningMessage}
{updateThroughputBeyondLimitWarningMessage} {updateThroughputBeyondLimitWarningMessage}
{updateThroughputDelayedApplyWarningMessage} {updateThroughputDelayedApplyWarningMessage}

View File

@ -36,7 +36,7 @@ import {
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import { isDirtyTypes, isDirty } from "./SettingsUtils"; import { isDirtyTypes, isDirty } from "./SettingsUtils";
const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } }; export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = { export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
label: { label: {
@ -245,15 +245,9 @@ export const ttlWarning: JSX.Element = (
</Text> </Text>
); );
export const indexingPolicyTTLWarningMessage: JSX.Element = ( export const indexingPolicynUnsavedWarningMessage: JSX.Element = (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>
Changing the Indexing Policy impacts query results while the index transformation occurs. When a change is made and You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. For
more information see,{" "}
<Link target="_blank" href="https://aka.ms/cosmosdb/modify-index-policy">
Modifying Indexing Policies
</Link>
.
</Text> </Text>
); );
@ -410,8 +404,8 @@ export const mongoIndexingPolicyAADError: JSX.Element = (
export const mongoIndexTransformationRefreshingMessage: JSX.Element = ( export const mongoIndexTransformationRefreshingMessage: JSX.Element = (
<Stack horizontal {...mongoWarningStackProps}> <Stack horizontal {...mongoWarningStackProps}>
<Text>Refreshing index transformation progress</Text> <Text styles={infoAndToolTipTextStyle}>Refreshing index transformation progress</Text>
<Spinner size={SpinnerSize.medium} /> <Spinner size={SpinnerSize.small} />
</Stack> </Stack>
); );
@ -421,14 +415,14 @@ export const renderMongoIndexTransformationRefreshMessage = (
): JSX.Element => { ): JSX.Element => {
if (progress === 0) { if (progress === 0) {
return ( return (
<Text> <Text styles={infoAndToolTipTextStyle}>
{"You can make more indexing changes once the current index transformation is complete. "} {"You can make more indexing changes once the current index transformation is complete. "}
<Link onClick={performRefresh}>{"Refresh to check if it has completed."}</Link> <Link onClick={performRefresh}>{"Refresh to check if it has completed."}</Link>
</Text> </Text>
); );
} else { } else {
return ( return (
<Text> <Text styles={infoAndToolTipTextStyle}>
{`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `} {`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `}
<Link onClick={performRefresh}>{"Refresh to check the progress."}</Link> <Link onClick={performRefresh}>{"Refresh to check the progress."}</Link>
</Text> </Text>

View File

@ -25,7 +25,9 @@ describe("IndexingPolicyComponent", () => {
}, },
onIndexingPolicyDirtyChange: () => { onIndexingPolicyDirtyChange: () => {
return; return;
} },
indexTransformationProgress: undefined,
refreshIndexTransformationProgress: () => new Promise(jest.fn())
}; };
it("renders", () => { it("renders", () => {

View File

@ -1,9 +1,10 @@
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import { isDirty } from "../SettingsUtils"; import { isDirty, isIndexTransforming } from "../SettingsUtils";
import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react"; import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react";
import { indexingPolicyTTLWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils"; import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
export interface IndexingPolicyComponentProps { export interface IndexingPolicyComponentProps {
shouldDiscardIndexingPolicy: boolean; shouldDiscardIndexingPolicy: boolean;
@ -12,6 +13,8 @@ export interface IndexingPolicyComponentProps {
indexingPolicyContentBaseline: DataModels.IndexingPolicy; indexingPolicyContentBaseline: DataModels.IndexingPolicy;
onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void; onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void;
logIndexingPolicySuccessMessage: () => void; logIndexingPolicySuccessMessage: () => void;
indexTransformationProgress: number;
refreshIndexTransformationProgress: () => Promise<void>;
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void; onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
} }
@ -51,6 +54,9 @@ export class IndexingPolicyComponent extends React.Component<
if (!this.indexingPolicyEditor) { if (!this.indexingPolicyEditor) {
this.createIndexingPolicyEditor(); this.createIndexingPolicyEditor();
} else { } else {
this.indexingPolicyEditor.updateOptions({
readOnly: isIndexTransforming(this.props.indexTransformationProgress)
});
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel(); const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4); const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
indexingPolicyEditorModel.setValue(value); indexingPolicyEditorModel.setValue(value);
@ -84,7 +90,7 @@ export class IndexingPolicyComponent extends React.Component<
this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, { this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, {
value: value, value: value,
language: "json", language: "json",
readOnly: false, readOnly: isIndexTransforming(this.props.indexTransformationProgress),
ariaLabel: "Indexing Policy" ariaLabel: "Indexing Policy"
}); });
if (this.indexingPolicyEditor) { if (this.indexingPolicyEditor) {
@ -108,8 +114,12 @@ export class IndexingPolicyComponent extends React.Component<
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<Stack {...titleAndInputStackProps}> <Stack {...titleAndInputStackProps}>
<IndexingPolicyRefreshComponent
indexTransformationProgress={this.props.indexTransformationProgress}
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
/>
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && ( {isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicyTTLWarningMessage}</MessageBar> <MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar>
)} )}
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div> <div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
</Stack> </Stack>

View File

@ -0,0 +1,15 @@
import { shallow } from "enzyme";
import React from "react";
import { IndexingPolicyRefreshComponentProps, IndexingPolicyRefreshComponent } from "./IndexingPolicyRefreshComponent";
describe("IndexingPolicyRefreshComponent", () => {
it("renders", () => {
const props: IndexingPolicyRefreshComponentProps = {
indexTransformationProgress: 90,
refreshIndexTransformationProgress: () => new Promise(jest.fn())
};
const wrapper = shallow(<IndexingPolicyRefreshComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,62 @@
import * as React from "react";
import { MessageBar, MessageBarType } from "office-ui-fabric-react";
import {
mongoIndexTransformationRefreshingMessage,
renderMongoIndexTransformationRefreshMessage
} from "../../SettingsRenderUtils";
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
import { isIndexTransforming } from "../../SettingsUtils";
export interface IndexingPolicyRefreshComponentProps {
indexTransformationProgress: number;
refreshIndexTransformationProgress: () => Promise<void>;
}
interface IndexingPolicyRefreshComponentState {
isRefreshing: boolean;
}
export class IndexingPolicyRefreshComponent extends React.Component<
IndexingPolicyRefreshComponentProps,
IndexingPolicyRefreshComponentState
> {
constructor(props: IndexingPolicyRefreshComponentProps) {
super(props);
this.state = {
isRefreshing: false
};
}
private onClickRefreshIndexingTransformationLink = async () => await this.refreshIndexTransformationProgress();
private renderIndexTransformationWarning = (): JSX.Element => {
if (this.state.isRefreshing) {
return mongoIndexTransformationRefreshingMessage;
} else if (isIndexTransforming(this.props.indexTransformationProgress)) {
return renderMongoIndexTransformationRefreshMessage(
this.props.indexTransformationProgress,
this.onClickRefreshIndexingTransformationLink
);
}
return undefined;
};
private refreshIndexTransformationProgress = async () => {
this.setState({ isRefreshing: true });
try {
await this.props.refreshIndexTransformationProgress();
} catch (error) {
handleError(error, "Refreshing index transformation progress failed.", "RefreshIndexTransformationProgress");
} finally {
this.setState({ isRefreshing: false });
}
};
public render(): JSX.Element {
return this.renderIndexTransformationWarning() ? (
<MessageBar messageBarType={MessageBarType.warning}>{this.renderIndexTransformationWarning()}</MessageBar>
) : (
<></>
);
}
}

View File

@ -0,0 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`IndexingPolicyRefreshComponent renders 1`] = `
<StyledMessageBarBase
messageBarType={5}
>
<Text
styles={
Object {
"root": Object {
"fontSize": 12,
},
}
}
>
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
<StyledLinkBase
onClick={[Function]}
>
Refresh to check the progress.
</StyledLinkBase>
</Text>
</StyledMessageBarBase>
`;

View File

@ -2,6 +2,7 @@ import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils"; import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils";
import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent"; import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent";
import { renderToString } from "react-dom/server";
describe("MongoIndexingPolicyComponent", () => { describe("MongoIndexingPolicyComponent", () => {
const baseProps: MongoIndexingPolicyComponentProps = { const baseProps: MongoIndexingPolicyComponentProps = {
@ -21,10 +22,7 @@ describe("MongoIndexingPolicyComponent", () => {
return; return;
}, },
indexTransformationProgress: undefined, indexTransformationProgress: undefined,
refreshIndexTransformationProgress: () => refreshIndexTransformationProgress: () => new Promise(jest.fn()),
new Promise(() => {
return;
}),
onMongoIndexingPolicySaveableChange: () => { onMongoIndexingPolicySaveableChange: () => {
return; return;
}, },
@ -38,16 +36,6 @@ describe("MongoIndexingPolicyComponent", () => {
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
it("isIndexingTransforming", () => {
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(false);
wrapper.setProps({ indexTransformationProgress: 50 });
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(true);
wrapper.setProps({ indexTransformationProgress: 100 });
expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(false);
});
describe("AddMongoIndexProps test", () => { describe("AddMongoIndexProps test", () => {
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />); const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent; const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
@ -55,7 +43,7 @@ describe("MongoIndexingPolicyComponent", () => {
it("defaults", () => { it("defaults", () => {
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(false); expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(false);
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(false); expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(false);
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual(undefined); expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
}); });
const sampleWarning = "sampleWarning"; const sampleWarning = "sampleWarning";
@ -113,9 +101,12 @@ describe("MongoIndexingPolicyComponent", () => {
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual( expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(
isMongoIndexingPolicyDiscardable isMongoIndexingPolicyDiscardable
); );
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual( if (mongoWarningNotificationMessage) {
mongoWarningNotificationMessage const elementAsString = renderToString(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage());
); expect(elementAsString).toContain(mongoWarningNotificationMessage);
} else {
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
}
} }
); );
}); });

View File

@ -25,8 +25,8 @@ import {
createAndAddMongoIndexStackProps, createAndAddMongoIndexStackProps,
separatorStyles, separatorStyles,
mongoIndexingPolicyAADError, mongoIndexingPolicyAADError,
mongoIndexTransformationRefreshingMessage, indexingPolicynUnsavedWarningMessage,
renderMongoIndexTransformationRefreshMessage infoAndToolTipTextStyle
} from "../../SettingsRenderUtils"; } from "../../SettingsRenderUtils";
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types"; import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
import { import {
@ -35,12 +35,13 @@ import {
MongoIndexIdField, MongoIndexIdField,
MongoNotificationType, MongoNotificationType,
getMongoIndexType, getMongoIndexType,
getMongoIndexTypeText getMongoIndexTypeText,
isIndexTransforming
} from "../../SettingsUtils"; } from "../../SettingsUtils";
import { AddMongoIndexComponent } from "./AddMongoIndexComponent"; import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent"; import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
import { AuthType } from "../../../../../AuthType"; import { AuthType } from "../../../../../AuthType";
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
export interface MongoIndexingPolicyComponentProps { export interface MongoIndexingPolicyComponentProps {
mongoIndexes: MongoIndex[]; mongoIndexes: MongoIndex[];
@ -56,20 +57,13 @@ export interface MongoIndexingPolicyComponentProps {
onMongoIndexingPolicyDiscardableChange: (isMongoIndexingPolicyDiscardable: boolean) => void; onMongoIndexingPolicyDiscardableChange: (isMongoIndexingPolicyDiscardable: boolean) => void;
} }
interface MongoIndexingPolicyComponentState {
isRefreshingIndexTransformationProgress: boolean;
}
interface MongoIndexDisplayProps { interface MongoIndexDisplayProps {
definition: JSX.Element; definition: JSX.Element;
type: JSX.Element; type: JSX.Element;
actionButton: JSX.Element; actionButton: JSX.Element;
} }
export class MongoIndexingPolicyComponent extends React.Component< export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingPolicyComponentProps> {
MongoIndexingPolicyComponentProps,
MongoIndexingPolicyComponentState
> {
private shouldCheckComponentIsDirty = true; private shouldCheckComponentIsDirty = true;
private addMongoIndexComponentRefs: React.RefObject<AddMongoIndexComponent>[] = []; private addMongoIndexComponentRefs: React.RefObject<AddMongoIndexComponent>[] = [];
private initialIndexesColumns: IColumn[] = [ private initialIndexesColumns: IColumn[] = [
@ -98,13 +92,6 @@ export class MongoIndexingPolicyComponent extends React.Component<
} }
]; ];
constructor(props: MongoIndexingPolicyComponentProps) {
super(props);
this.state = {
isRefreshingIndexTransformationProgress: false
};
}
componentDidUpdate(prevProps: MongoIndexingPolicyComponentProps): void { componentDidUpdate(prevProps: MongoIndexingPolicyComponentProps): void {
if (this.props.indexesToAdd.length > prevProps.indexesToAdd.length) { if (this.props.indexesToAdd.length > prevProps.indexesToAdd.length) {
this.addMongoIndexComponentRefs[prevProps.indexesToAdd.length]?.current?.focus(); this.addMongoIndexComponentRefs[prevProps.indexesToAdd.length]?.current?.focus();
@ -144,10 +131,15 @@ export class MongoIndexingPolicyComponent extends React.Component<
return this.props.indexesToAdd.length > 0 || this.props.indexesToDrop.length > 0; return this.props.indexesToAdd.length > 0 || this.props.indexesToDrop.length > 0;
}; };
public getMongoWarningNotificationMessage = (): string => { public getMongoWarningNotificationMessage = (): JSX.Element => {
return this.props.indexesToAdd.find( const warningMessage = this.props.indexesToAdd.find(
addMongoIndexProps => addMongoIndexProps.notification?.type === MongoNotificationType.Warning addMongoIndexProps => addMongoIndexProps.notification?.type === MongoNotificationType.Warning
)?.notification.message; )?.notification.message;
if (warningMessage) {
return <Text styles={infoAndToolTipTextStyle}>{warningMessage}</Text>;
}
return undefined;
}; };
private onRenderRow = (props: IDetailsRowProps): JSX.Element => { private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
@ -159,7 +151,7 @@ export class MongoIndexingPolicyComponent extends React.Component<
<IconButton <IconButton
ariaLabel="Delete index Button" ariaLabel="Delete index Button"
iconProps={{ iconName: "Delete" }} iconProps={{ iconName: "Delete" }}
disabled={this.isIndexingTransforming()} disabled={isIndexTransforming(this.props.indexTransformationProgress)}
onClick={() => { onClick={() => {
this.props.onIndexDrop(arrayPosition); this.props.onIndexDrop(arrayPosition);
}} }}
@ -230,7 +222,7 @@ export class MongoIndexingPolicyComponent extends React.Component<
<AddMongoIndexComponent <AddMongoIndexComponent
ref={this.addMongoIndexComponentRefs[indexesToAddLength]} ref={this.addMongoIndexComponentRefs[indexesToAddLength]}
disabled={this.isIndexingTransforming()} disabled={isIndexTransforming(this.props.indexTransformationProgress)}
position={indexesToAddLength} position={indexesToAddLength}
key={indexesToAddLength} key={indexesToAddLength}
description={undefined} description={undefined}
@ -298,55 +290,21 @@ export class MongoIndexingPolicyComponent extends React.Component<
); );
}; };
private refreshIndexTransformationProgress = async () => {
this.setState({ isRefreshingIndexTransformationProgress: true });
try {
await this.props.refreshIndexTransformationProgress();
} catch (error) {
handleError(error, "Refreshing index transformation progress failed.", "RefreshIndexTransformationProgress");
} finally {
this.setState({ isRefreshingIndexTransformationProgress: false });
}
};
public isIndexingTransforming = (): boolean =>
// index transformation progress can be 0
this.props.indexTransformationProgress !== undefined && this.props.indexTransformationProgress !== 100;
private onClickRefreshIndexingTransformationLink = async () => await this.refreshIndexTransformationProgress();
private renderIndexTransformationWarning = (): JSX.Element => {
if (this.state.isRefreshingIndexTransformationProgress) {
return mongoIndexTransformationRefreshingMessage;
} else if (this.isIndexingTransforming()) {
return renderMongoIndexTransformationRefreshMessage(
this.props.indexTransformationProgress,
this.onClickRefreshIndexingTransformationLink
);
}
return undefined;
};
private renderWarningMessage = (): JSX.Element => { private renderWarningMessage = (): JSX.Element => {
let warningMessage: string; let warningMessage: JSX.Element;
if (this.getMongoWarningNotificationMessage()) { if (this.getMongoWarningNotificationMessage()) {
warningMessage = this.getMongoWarningNotificationMessage(); warningMessage = this.getMongoWarningNotificationMessage();
} else if (this.isMongoIndexingPolicySaveable()) { } else if (this.isMongoIndexingPolicySaveable()) {
warningMessage = warningMessage = indexingPolicynUnsavedWarningMessage;
"You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.";
} }
return ( return (
<> <>
{this.renderIndexTransformationWarning() && ( <IndexingPolicyRefreshComponent
<MessageBar messageBarType={MessageBarType.warning}>{this.renderIndexTransformationWarning()}</MessageBar> indexTransformationProgress={this.props.indexTransformationProgress}
)} refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
/>
{warningMessage && ( {warningMessage && <MessageBar messageBarType={MessageBarType.warning}>{warningMessage}</MessageBar>}
<MessageBar messageBarType={MessageBarType.warning}>
<Text>{warningMessage}</Text>
</MessageBar>
)}
</> </>
); );
}; };

View File

@ -8,6 +8,9 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
} }
} }
> >
<IndexingPolicyRefreshComponent
refreshIndexTransformationProgress={[Function]}
/>
<Text> <Text>
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index. For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
<StyledLinkBase <StyledLinkBase

View File

@ -8,6 +8,9 @@ exports[`IndexingPolicyComponent renders 1`] = `
} }
} }
> >
<IndexingPolicyRefreshComponent
refreshIndexTransformationProgress={[Function]}
/>
<div <div
className="settingsV2IndexingPolicyEditor" className="settingsV2IndexingPolicyEditor"
tabIndex={0} tabIndex={0}

View File

@ -15,7 +15,8 @@ import {
MongoWildcardPlaceHolder, MongoWildcardPlaceHolder,
getMongoIndexTypeText, getMongoIndexTypeText,
SingleFieldText, SingleFieldText,
WildcardText WildcardText,
isIndexTransforming
} from "./SettingsUtils"; } from "./SettingsUtils";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
@ -139,3 +140,10 @@ describe("SettingsUtils", () => {
expect(notification.type).toEqual(MongoNotificationType.Error); expect(notification.type).toEqual(MongoNotificationType.Error);
}); });
}); });
it("isIndexingTransforming", () => {
expect(isIndexTransforming(undefined)).toBeFalsy();
expect(isIndexTransforming(0)).toBeTruthy();
expect(isIndexTransforming(90)).toBeTruthy();
expect(isIndexTransforming(100)).toBeFalsy();
});

View File

@ -250,3 +250,7 @@ export const getMongoIndexTypeText = (index: MongoIndexTypes): string => {
} }
return WildcardText; return WildcardText;
}; };
export const isIndexTransforming = (indexTransformationProgress: number): boolean =>
// index transformation progress can be 0
indexTransformationProgress !== undefined && indexTransformationProgress !== 100;

View File

@ -5189,6 +5189,7 @@ exports[`SettingsComponent renders 1`] = `
logIndexingPolicySuccessMessage={[Function]} logIndexingPolicySuccessMessage={[Function]}
onIndexingPolicyContentChange={[Function]} onIndexingPolicyContentChange={[Function]}
onIndexingPolicyDirtyChange={[Function]} onIndexingPolicyDirtyChange={[Function]}
refreshIndexTransformationProgress={[Function]}
resetShouldDiscardIndexingPolicy={[Function]} resetShouldDiscardIndexingPolicy={[Function]}
shouldDiscardIndexingPolicy={false} shouldDiscardIndexingPolicy={false}
/> />

View File

@ -166,15 +166,7 @@ exports[`SettingsUtils functions render 1`] = `
} }
} }
> >
Changing the Indexing Policy impacts query results while the index transformation occurs. When a change is made and the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. For more information see, You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
<StyledLinkBase
href="https://aka.ms/cosmosdb/modify-index-policy"
target="_blank"
>
Modifying Indexing Policies
</StyledLinkBase>
.
</Text> </Text>
<Text <Text
id="updateThroughputBeyondLimitWarningMessage" id="updateThroughputBeyondLimitWarningMessage"
@ -341,14 +333,30 @@ exports[`SettingsUtils functions render 1`] = `
} }
} }
> >
<Text> <Text
styles={
Object {
"root": Object {
"fontSize": 12,
},
}
}
>
Refreshing index transformation progress Refreshing index transformation progress
</Text> </Text>
<StyledSpinnerBase <StyledSpinnerBase
size={2} size={1}
/> />
</Stack> </Stack>
<Text> <Text
styles={
Object {
"root": Object {
"fontSize": 12,
},
}
}
>
You can make more indexing changes once the current index transformation is complete. You can make more indexing changes once the current index transformation is complete.
<StyledLinkBase <StyledLinkBase
onClick={[Function]} onClick={[Function]}
@ -356,7 +364,15 @@ exports[`SettingsUtils functions render 1`] = `
Refresh to check if it has completed. Refresh to check if it has completed.
</StyledLinkBase> </StyledLinkBase>
</Text> </Text>
<Text> <Text
styles={
Object {
"root": Object {
"fontSize": 12,
},
}
}
>
You can make more indexing changes once the current index transformation has completed. It is 90% complete. You can make more indexing changes once the current index transformation has completed. It is 90% complete.
<StyledLinkBase <StyledLinkBase
onClick={[Function]} onClick={[Function]}