Removed SettingsV1 code paths (#325)
* removed settingsv1 code path in collection.ts * removed Settingsv1 code * Moved AAD error message up the chain
This commit is contained in:
parent
84b6075ee8
commit
85d2378d3a
|
@ -202,8 +202,6 @@ src/Explorer/Tabs/QueryTab.test.ts
|
|||
src/Explorer/Tabs/QueryTab.ts
|
||||
src/Explorer/Tabs/QueryTablesTab.ts
|
||||
src/Explorer/Tabs/ScriptTabBase.ts
|
||||
src/Explorer/Tabs/SettingsTab.test.ts
|
||||
src/Explorer/Tabs/SettingsTab.ts
|
||||
src/Explorer/Tabs/SparkMasterTab.ts
|
||||
src/Explorer/Tabs/StoredProcedureTab.ts
|
||||
src/Explorer/Tabs/TabComponents.ts
|
||||
|
|
|
@ -16,7 +16,7 @@ const notificationsPath = () => {
|
|||
};
|
||||
|
||||
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
|
||||
if (configContext.platform === Platform.Emulator) {
|
||||
if (configContext.platform === Platform.Emulator || configContext.platform === Platform.Hosted) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
@ -44,10 +44,6 @@ describe("Component Registerer", () => {
|
|||
expect(ko.components.isRegistered("user-defined-function-tab")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register settings-tab component", () => {
|
||||
expect(ko.components.isRegistered("settings-tab")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register settings-tab-v2 component", () => {
|
||||
expect(ko.components.isRegistered("settings-tab-v2")).toBe(true);
|
||||
});
|
||||
|
|
|
@ -31,7 +31,6 @@ ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTa
|
|||
ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab());
|
||||
ko.components.register("trigger-tab", new TabComponents.TriggerTab());
|
||||
ko.components.register("user-defined-function-tab", new TabComponents.UserDefinedFunctionTab());
|
||||
ko.components.register("settings-tab", new TabComponents.SettingsTab());
|
||||
ko.components.register("settings-tab-v2", new TabComponents.SettingsTabV2());
|
||||
ko.components.register("query-tab", new TabComponents.QueryTab());
|
||||
ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab());
|
||||
|
|
|
@ -16,7 +16,7 @@ import { CommandButtonComponentProps } from "../../Controls/CommandButton/Comman
|
|||
import { userContext } from "../../../UserContext";
|
||||
import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
||||
import SettingsTab from "../../Tabs/SettingsTabV2";
|
||||
import { throughputUnit } from "./SettingsRenderUtils";
|
||||
import { mongoIndexingPolicyAADError, throughputUnit } from "./SettingsRenderUtils";
|
||||
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
||||
import {
|
||||
MongoIndexingPolicyComponent,
|
||||
|
@ -49,6 +49,7 @@ import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/genera
|
|||
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
|
||||
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
import { isEmpty } from "underscore";
|
||||
|
||||
interface SettingsV2TabInfo {
|
||||
tab: SettingsV2TabTypes;
|
||||
|
@ -227,7 +228,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||
|
||||
public loadMongoIndexes = async (): Promise<void> => {
|
||||
if (
|
||||
this.container.isMongoIndexEditorEnabled() &&
|
||||
this.container.isPreferredApiMongoDB() &&
|
||||
this.container.isEnableMongoCapabilityPresent() &&
|
||||
this.container.databaseAccount()
|
||||
|
@ -1000,16 +1000,19 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />
|
||||
});
|
||||
} else if (
|
||||
this.container.isMongoIndexEditorEnabled() &&
|
||||
this.container.isPreferredApiMongoDB() &&
|
||||
this.container.isEnableMongoCapabilityPresent()
|
||||
) {
|
||||
} else if (this.container.isPreferredApiMongoDB()) {
|
||||
if (isEmpty(this.container.features())) {
|
||||
tabs.push({
|
||||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||
content: mongoIndexingPolicyAADError
|
||||
});
|
||||
} else if (this.container.isEnableMongoCapabilityPresent()) {
|
||||
tabs.push({
|
||||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasConflictResolution()) {
|
||||
tabs.push({
|
||||
|
|
|
@ -24,7 +24,6 @@ import {
|
|||
transparentDetailsRowStyles,
|
||||
createAndAddMongoIndexStackProps,
|
||||
separatorStyles,
|
||||
mongoIndexingPolicyAADError,
|
||||
indexingPolicynUnsavedWarningMessage,
|
||||
infoAndToolTipTextStyle
|
||||
} from "../../SettingsRenderUtils";
|
||||
|
@ -40,7 +39,6 @@ import {
|
|||
} from "../../SettingsUtils";
|
||||
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
||||
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
|
||||
import { AuthType } from "../../../../../AuthType";
|
||||
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||
|
||||
export interface MongoIndexingPolicyComponentProps {
|
||||
|
@ -321,7 +319,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||
</Stack>
|
||||
);
|
||||
} else {
|
||||
return window.authType !== AuthType.AAD ? mongoIndexingPolicyAADError : <Spinner size={SpinnerSize.large} />;
|
||||
return <Spinner size={SpinnerSize.large} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -956,7 +956,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
|
@ -973,7 +972,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||
"isRightPanelV2Enabled": [Function],
|
||||
"isSchemaEnabled": [Function],
|
||||
"isServerlessEnabled": [Function],
|
||||
"isSettingsV2Enabled": [Function],
|
||||
"isSparkEnabled": [Function],
|
||||
"isSparkEnabledForAccount": [Function],
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
|
@ -2237,7 +2235,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
|
@ -2254,7 +2251,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||
"isRightPanelV2Enabled": [Function],
|
||||
"isSchemaEnabled": [Function],
|
||||
"isServerlessEnabled": [Function],
|
||||
"isSettingsV2Enabled": [Function],
|
||||
"isSparkEnabled": [Function],
|
||||
"isSparkEnabledForAccount": [Function],
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
|
@ -3531,7 +3527,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
|
@ -3548,7 +3543,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||
"isRightPanelV2Enabled": [Function],
|
||||
"isSchemaEnabled": [Function],
|
||||
"isServerlessEnabled": [Function],
|
||||
"isSettingsV2Enabled": [Function],
|
||||
"isSparkEnabled": [Function],
|
||||
"isSparkEnabledForAccount": [Function],
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
|
@ -4812,7 +4806,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||
"isHostedDataExplorerEnabled": [Function],
|
||||
"isLeftPaneExpanded": [Function],
|
||||
"isLinkInjectionEnabled": [Function],
|
||||
"isMongoIndexEditorEnabled": [Function],
|
||||
"isNotebookEnabled": [Function],
|
||||
"isNotebooksEnabledForAccount": [Function],
|
||||
"isNotificationConsoleExpanded": [Function],
|
||||
|
@ -4829,7 +4822,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||
"isRightPanelV2Enabled": [Function],
|
||||
"isSchemaEnabled": [Function],
|
||||
"isServerlessEnabled": [Function],
|
||||
"isSettingsV2Enabled": [Function],
|
||||
"isSparkEnabled": [Function],
|
||||
"isSparkEnabledForAccount": [Function],
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
|
|
|
@ -206,8 +206,6 @@ export default class Explorer {
|
|||
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
||||
public isCodeOfConductEnabled: ko.Computed<boolean>;
|
||||
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
||||
public isSettingsV2Enabled: ko.Observable<boolean>;
|
||||
public isMongoIndexEditorEnabled: ko.Observable<boolean>;
|
||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
|
||||
|
@ -412,8 +410,6 @@ export default class Explorer {
|
|||
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
||||
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
||||
);
|
||||
this.isSettingsV2Enabled = ko.observable(false);
|
||||
this.isMongoIndexEditorEnabled = ko.observable(false);
|
||||
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
||||
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||
|
@ -1913,14 +1909,6 @@ export default class Explorer {
|
|||
if (!flights) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) {
|
||||
this.isSettingsV2Enabled(true);
|
||||
}
|
||||
|
||||
if (flights.indexOf(Constants.Flights.MongoIndexEditor) !== -1) {
|
||||
this.isMongoIndexEditorEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public findSelectedCollection(): ViewModels.Collection {
|
||||
|
|
|
@ -1,723 +0,0 @@
|
|||
<div
|
||||
class="tab-pane flexContainer"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: tabId
|
||||
},
|
||||
visible: isActive"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div class="warningErrorContainer scaleWarningContainer" data-bind="visible: shouldShowStatusBar">
|
||||
<div>
|
||||
<div class="warningErrorContent" data-bind="visible: shouldShowNotificationStatusPrompt">
|
||||
<span><img src="/info_color.svg" alt="Info"/></span>
|
||||
<span class="warningErrorDetailsLinkContainer" data-bind="html: notificationStatusInfo"></span>
|
||||
</div>
|
||||
<div class="warningErrorContent" data-bind="visible: !shouldShowNotificationStatusPrompt()">
|
||||
<span><img src="/warning.svg" alt="Warning"/></span>
|
||||
<span class="warningErrorDetailsLinkContainer" data-bind="html: warningMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabForm scaleSettingScrollable">
|
||||
<!-- ko if: shouldShowKeyspaceSharedThroughputMessage -->
|
||||
<div>This table shared throughput is configured at the keyspace</div>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko ifnot: hasDatabaseSharedThroughput -->
|
||||
<div>
|
||||
<div
|
||||
class="scaleDivison"
|
||||
data-bind="click:toggleScale, event: { keypress: onScaleKeyPress }, attr:{ 'aria-expanded': scaleExpanded() ? 'true' : 'false' }"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Scale"
|
||||
aria-controls="scaleRegion"
|
||||
>
|
||||
<span class="themed-images" type="text/html" id="ExpandChevronRightScale" data-bind="visible: !scaleExpanded()">
|
||||
<img
|
||||
class="imgiconwidth ssExpandCollapseIcon ssCollapseIcon "
|
||||
src="/Triangle-right.svg"
|
||||
alt="Show scale properties"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span class="themed-images" type="text/html" id="ExpandChevronDownScale" data-bind="visible: scaleExpanded">
|
||||
<img class="imgiconwidth ssExpandCollapseIcon " src="/Triangle-down.svg" alt="Hide scale properties" />
|
||||
</span>
|
||||
|
||||
<span class="scaleSettingTitle">Scale</span>
|
||||
</div>
|
||||
|
||||
<div class="ssTextAllignment" data-bind="visible: scaleExpanded" id="scaleRegion">
|
||||
<!-- ko ifnot: isAutoScaleEnabled -->
|
||||
<throughput-input-autopilot-v3
|
||||
params="{
|
||||
testId: testId,
|
||||
class: 'scaleForm dirty',
|
||||
value: throughput,
|
||||
minimum: minRUs,
|
||||
maximum: maxRUThroughputInputLimit,
|
||||
isEnabled: !hasDatabaseSharedThroughput(),
|
||||
canExceedMaximumValue: canThroughputExceedMaximumValue,
|
||||
label: throughputTitle,
|
||||
ariaLabel: throughputAriaLabel,
|
||||
costsVisible: costsVisible,
|
||||
requestUnitsUsageCost: requestUnitsUsageCost,
|
||||
throughputAutoPilotRadioId: throughputAutoPilotRadioId,
|
||||
throughputProvisionedRadioId: throughputProvisionedRadioId,
|
||||
throughputModeRadioName: throughputModeRadioName,
|
||||
showAutoPilot: userCanChangeProvisioningTypes,
|
||||
isAutoPilotSelected: isAutoPilotSelected,
|
||||
maxAutoPilotThroughputSet: autoPilotThroughput,
|
||||
autoPilotUsageCost: autoPilotUsageCost,
|
||||
overrideWithAutoPilotSettings: overrideWithAutoPilotSettings,
|
||||
overrideWithProvisionedThroughputSettings: overrideWithProvisionedThroughputSettings
|
||||
}"
|
||||
>
|
||||
</throughput-input-autopilot-v3>
|
||||
|
||||
<div class="storageCapacityTitle throughputStorageValue" data-bind="html: storageCapacityTitle"></div>
|
||||
<!-- /ko -->
|
||||
|
||||
<div data-bind="visible: rupmVisible">
|
||||
<div class="formTitle">RU/m</div>
|
||||
<div class="tabs" aria-label="RU/m">
|
||||
<div class="tab">
|
||||
<label
|
||||
data-bind="
|
||||
attr:{
|
||||
for: rupmOnId
|
||||
},
|
||||
css: {
|
||||
dirty: rupm.editableIsDirty,
|
||||
selectedRadio: rupm() === 'on',
|
||||
unselectedRadio: rupm() !== 'on'
|
||||
}"
|
||||
>On</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="rupm"
|
||||
value="on"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: rupmOnId
|
||||
},
|
||||
checked: rupm"
|
||||
/>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<label
|
||||
data-bind="
|
||||
attr:{
|
||||
for: rupmOffId
|
||||
},
|
||||
css: {
|
||||
dirty: rupm.editableIsDirty,
|
||||
selectedRadio: rupm() === 'off',
|
||||
unselectedRadio: rupm() !== 'off'
|
||||
}"
|
||||
>Off</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="rupm"
|
||||
value="off"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: rupmOffId
|
||||
},
|
||||
checked: rupm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: Replace link with call to the Azure Support blade -->
|
||||
<div data-bind="visible: isAutoScaleEnabled">
|
||||
<div class="autoScaleThroughputTitle">Throughput (RU/s)</div>
|
||||
<input
|
||||
class="formReadOnly collid-white"
|
||||
readonly
|
||||
aria-label="Throughput input"
|
||||
data-bind="textInput: throughput"
|
||||
/>
|
||||
<div class="autoScaleDescription">
|
||||
Your account has custom settings that prevents setting throughput at the container level. Please work with
|
||||
your Cosmos DB engineering team point of contact to make changes.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
|
||||
<div data-bind="visible: hasConflictResolution">
|
||||
<div
|
||||
class="formTitle"
|
||||
data-bind="click:toggleConflictResolution, event: { keypress: onConflictResolutionKeyPress }, attr:{ 'aria-expanded': conflictResolutionExpanded() ? 'true' : 'false' }"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Conflict Resolution"
|
||||
aria-controls="conflictResolutionRegion"
|
||||
>
|
||||
<span
|
||||
class="themed-images"
|
||||
type="text/html"
|
||||
id="ExpandChevronRightConflictResolution"
|
||||
data-bind="visible: !conflictResolutionExpanded()"
|
||||
>
|
||||
<img
|
||||
class="imgiconwidth ssExpandCollapseIcon ssCollapseIcon"
|
||||
src="/Triangle-right.svg"
|
||||
alt="Show conflict resolution"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="themed-images"
|
||||
type="text/html"
|
||||
id="ExpandChevronDownConflictResolution"
|
||||
data-bind="visible: conflictResolutionExpanded"
|
||||
>
|
||||
<img class="imgiconwidth ssExpandCollapseIcon" src="/Triangle-down.svg" alt="Show conflict resolution" />
|
||||
</span>
|
||||
<span class="scaleSettingTitle">Conflict resolution</span>
|
||||
</div>
|
||||
<div id="conflictResolutionRegion" class="ssTextAllignment" data-bind="visible: conflictResolutionExpanded">
|
||||
<div class="formTitle">Mode</div>
|
||||
<div class="tabs" aria-label="Mode" role="radiogroup">
|
||||
<div class="tab">
|
||||
<label
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
for: conflictResolutionPolicyModeLWW,
|
||||
'aria-checked': conflictResolutionPolicyMode() !== 'Custom' ? 'true' : 'false'
|
||||
},
|
||||
css: {
|
||||
dirty: conflictResolutionPolicyMode.editableIsDirty,
|
||||
selectedRadio: conflictResolutionPolicyMode() === 'LastWriterWins',
|
||||
unselectedRadio: conflictResolutionPolicyMode() !== 'LastWriterWins'
|
||||
},
|
||||
event: {
|
||||
keypress: onConflictResolutionLWWKeyPress
|
||||
}"
|
||||
>Last Write Wins (default)</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="conflictresolution"
|
||||
value="LastWriterWins"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: conflictResolutionPolicyModeLWW
|
||||
},
|
||||
checked: conflictResolutionPolicyMode"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<label
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
for: conflictResolutionPolicyModeCustom,
|
||||
'aria-checked': conflictResolutionPolicyMode() === 'Custom' ? 'true' : 'false'
|
||||
},
|
||||
css: {
|
||||
dirty: conflictResolutionPolicyMode.editableIsDirty,
|
||||
selectedRadio: conflictResolutionPolicyMode() === 'Custom',
|
||||
unselectedRadio: conflictResolutionPolicyMode() !== 'Custom'
|
||||
},
|
||||
event: {
|
||||
keypress: onConflictResolutionCustomKeyPress
|
||||
}"
|
||||
>Merge Procedure (custom)</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="conflictresolution"
|
||||
value="Custom"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: conflictResolutionPolicyModeCustom
|
||||
},
|
||||
checked: conflictResolutionPolicyMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div data-bind="visible: conflictResolutionPolicyMode() === 'LastWriterWins'">
|
||||
<p class="formTitle">
|
||||
Conflict Resolver Property
|
||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
||||
<span class="tooltiptext infoTooltipWidth"
|
||||
>Gets or sets the name of a integer property in your documents which is used for the Last Write Wins
|
||||
(LWW) based conflict resolution scheme. By default, the system uses the system defined timestamp
|
||||
property, _ts to decide the winner for the conflicting versions of the document. Specify your own
|
||||
integer property if you want to override the default timestamp based conflict resolution.</span
|
||||
>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<input
|
||||
type="text"
|
||||
aria-label="Document path for conflict resolution"
|
||||
data-bind="
|
||||
css: {
|
||||
dirty: conflictResolutionPolicyPath.editableIsDirty
|
||||
},
|
||||
textInput: conflictResolutionPolicyPath,
|
||||
enable: conflictResolutionPolicyMode() === 'LastWriterWins'"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div data-bind="visible: conflictResolutionPolicyMode() === 'Custom'">
|
||||
<p class="formTitle">
|
||||
Stored procedure
|
||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
||||
<span class="tooltiptext infoTooltipWidth"
|
||||
>Gets or sets the name of a stored procedure (aka merge procedure) for resolving the conflicts. You can
|
||||
write application defined logic to determine the winner of the conflicting versions of a document. The
|
||||
stored procedure will get executed transactionally, exactly once, on the server side. If you do not
|
||||
provide a stored procedure, the conflicts will be populated in the
|
||||
<a class="linkDarkBackground" href="https://aka.ms/dataexplorerconflics" target="_blank"
|
||||
>conflicts feed</a
|
||||
>. You can update/re-register the stored procedure at any time.</span
|
||||
>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<input
|
||||
type="text"
|
||||
aria-label="Stored procedure name for conflict resolution"
|
||||
data-bind="
|
||||
css: {
|
||||
dirty: conflictResolutionPolicyProcedure.editableIsDirty
|
||||
},
|
||||
textInput: conflictResolutionPolicyProcedure,
|
||||
enable: conflictResolutionPolicyMode() === 'Custom'"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="formTitle"
|
||||
data-bind="click:toggleSettings, event: { keypress: onSettingsKeyPress }, attr:{ 'aria-expanded': settingsExpanded() ? 'true' : 'false' }, visible: shouldShowIndexingPolicyEditor() || ttlVisible()"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Settings"
|
||||
aria-controls="settingsRegion"
|
||||
>
|
||||
<span
|
||||
class="themed-images"
|
||||
type="text/html"
|
||||
id="ExpandChevronRightSettings"
|
||||
data-bind="visible: !settingsExpanded() && !hasDatabaseSharedThroughput()"
|
||||
>
|
||||
<img class="imgiconwidth ssExpandCollapseIcon ssCollapseIcon" src="/Triangle-right.svg" alt="Show settings" />
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="themed-images"
|
||||
type="text/html"
|
||||
id="ExpandChevronDownSettings"
|
||||
data-bind="visible: settingsExpanded() && !hasDatabaseSharedThroughput()"
|
||||
>
|
||||
<img class="imgiconwidth ssExpandCollapseIcon" src="/Triangle-down.svg" alt="Show settings" />
|
||||
</span>
|
||||
<span class="scaleSettingTitle">Settings</span>
|
||||
</div>
|
||||
<div class="ssTextAllignment" data-bind="visible: settingsExpanded" id="settingsRegion">
|
||||
<div data-bind="visible: ttlVisible">
|
||||
<div class="formTitle">Time to Live</div>
|
||||
<div class="tabs disableFocusDefaults" aria-label="Time to Live" role="radiogroup">
|
||||
<div class="tab">
|
||||
<label
|
||||
class="ttlIndexingPolicyFocusElement"
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
for: ttlOffId,
|
||||
'aria-checked': timeToLive() === 'off' ? 'true' : 'false'
|
||||
},
|
||||
css: {
|
||||
dirty: timeToLive.editableIsDirty,
|
||||
selectedRadio: timeToLive() === 'off',
|
||||
unselectedRadio: timeToLive() !== 'off'
|
||||
},
|
||||
event: {
|
||||
keypress: onTtlOffKeyPress
|
||||
},
|
||||
hasFocus: ttlOffFocused"
|
||||
>Off</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="ttl"
|
||||
value="off"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: ttlOffId
|
||||
},
|
||||
checked: timeToLive"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<label
|
||||
class="ttlIndexingPolicyFocusElement"
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
for: ttlOnNoDefaultId,
|
||||
'aria-checked': timeToLive() === 'on-nodefault' ? 'true' : 'false'
|
||||
},
|
||||
css: {
|
||||
dirty: timeToLive.editableIsDirty,
|
||||
selectedRadio: timeToLive() === 'on-nodefault',
|
||||
unselectedRadio: timeToLive() !== 'on-nodefault'
|
||||
},
|
||||
event: {
|
||||
keypress: onTtlOnNoDefaultKeyPress
|
||||
},
|
||||
hasFocus: ttlOnDefaultFocused"
|
||||
>On (no default)</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="ttl"
|
||||
value="on-nodefault"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: ttlOnNoDefaultId
|
||||
},
|
||||
checked: timeToLive"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<label
|
||||
class="ttlIndexingPolicyFocusElement"
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
for="ttl3"
|
||||
data-bind="
|
||||
attr:{
|
||||
for: ttlOnId,
|
||||
'aria-checked': timeToLive() === 'on' ? 'true' : 'false'
|
||||
},
|
||||
css: {
|
||||
dirty: timeToLive.editableIsDirty,
|
||||
selectedRadio: timeToLive() === 'on',
|
||||
unselectedRadio: timeToLive() !== 'on'
|
||||
},
|
||||
event: {
|
||||
keypress: onTtlOnKeyPress
|
||||
},
|
||||
hasFocus: ttlOnFocused"
|
||||
>On</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="ttl"
|
||||
value="on"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: ttlOnId
|
||||
},
|
||||
checked: timeToLive"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div data-bind="visible: timeToLive() === 'on'">
|
||||
<input
|
||||
class="dirtyTextbox"
|
||||
type="number"
|
||||
required
|
||||
min="1"
|
||||
max="2147483647"
|
||||
aria-label="Time to live in seconds"
|
||||
data-bind="
|
||||
css: {
|
||||
dirty: timeToLive.editableIsDirty
|
||||
},
|
||||
textInput: timeToLiveSeconds,
|
||||
enable: timeToLive() === 'on'"
|
||||
/>
|
||||
second(s)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Geospatial - start -->
|
||||
<div data-bind="visible: geospatialVisible">
|
||||
<div class="formTitle">Geospatial Configuration</div>
|
||||
|
||||
<div class="tabs disableFocusDefaults" aria-label="Geospatial Configuration" role="radiogroup">
|
||||
<div class="tab">
|
||||
<label
|
||||
for="geography"
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
'aria-checked': geospatialConfigType().toLowerCase() !== GEOMETRY.toLowerCase() ? 'true' : 'false'
|
||||
},
|
||||
css: {
|
||||
dirty: geospatialConfigType.editableIsDirty,
|
||||
selectedRadio: geospatialConfigType().toLowerCase() !== GEOMETRY.toLowerCase(),
|
||||
unselectedRadio: geospatialConfigType().toLowerCase() === GEOMETRY.toLowerCase()
|
||||
},
|
||||
event: {
|
||||
keypress: onGeographyKeyPress
|
||||
}"
|
||||
>Geography</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="geospatial"
|
||||
id="geography"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr: {
|
||||
value: GEOGRAPHY
|
||||
},
|
||||
checked: geospatialConfigType"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<label
|
||||
for="geometry"
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
'aria-checked': geospatialConfigType().toLowerCase() === GEOMETRY.toLowerCase() ? 'true' : 'false'
|
||||
},
|
||||
css: {
|
||||
dirty: geospatialConfigType.editableIsDirty,
|
||||
selectedRadio: geospatialConfigType().toLowerCase() === GEOMETRY.toLowerCase(),
|
||||
unselectedRadio: geospatialConfigType().toLowerCase() !== GEOMETRY.toLowerCase()
|
||||
},
|
||||
event: {
|
||||
keypress: onGeometryKeyPress
|
||||
}"
|
||||
>Geometry</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="geospatial"
|
||||
id="geometry"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr: {
|
||||
value: GEOMETRY
|
||||
},
|
||||
checked: geospatialConfigType"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Geospatial - end -->
|
||||
|
||||
<div data-bind="visible: isAnalyticalStorageEnabled">
|
||||
<div class="formTitle">Analytical Storage Time to Live</div>
|
||||
<div class="tabs disableFocusDefaults" aria-label="Analytical Storage Time to Live" role="radiogroup">
|
||||
<div class="tab">
|
||||
<label tabindex="0" role="radio" class="disabledRadio">Off </label>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<label
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
for: 'analyticalStorageTtlOnNoDefaultId',
|
||||
'aria-checked': analyticalStorageTtlSelection() === 'on-nodefault' ? 'true' : 'false'
|
||||
},
|
||||
css: {
|
||||
dirty: analyticalStorageTtlSelection.editableIsDirty,
|
||||
selectedRadio: analyticalStorageTtlSelection() === 'on-nodefault',
|
||||
unselectedRadio: analyticalStorageTtlSelection() !== 'on-nodefault'
|
||||
},
|
||||
event: {
|
||||
keypress: onAnalyticalStorageTtlOnNoDefaultKeyPress
|
||||
}"
|
||||
>On (no default)
|
||||
</label>
|
||||
<input
|
||||
type="radio"
|
||||
name="analyticalStorageTtl"
|
||||
value="on-nodefault"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: 'analyticalStorageTtlOnNoDefaultId'
|
||||
},
|
||||
checked: analyticalStorageTtlSelection"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<label
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
for="ttl3"
|
||||
data-bind="
|
||||
attr:{
|
||||
for: 'analyticalStorageTtlOnId',
|
||||
'aria-checked': analyticalStorageTtlSelection() === 'on' ? 'true' : 'false'
|
||||
},
|
||||
css: {
|
||||
dirty: analyticalStorageTtlSelection.editableIsDirty,
|
||||
selectedRadio: analyticalStorageTtlSelection() === 'on',
|
||||
unselectedRadio: analyticalStorageTtlSelection() !== 'on'
|
||||
},
|
||||
event: {
|
||||
keypress: onAnalyticalStorageTtlOnKeyPress
|
||||
}"
|
||||
>On</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="analyticalStorageTtl"
|
||||
value="on"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: 'analyticalStorageTtlOnId'
|
||||
},
|
||||
checked: analyticalStorageTtlSelection"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div data-bind="visible: analyticalStorageTtlSelection() === 'on'">
|
||||
<input
|
||||
class="dirtyTextbox"
|
||||
type="number"
|
||||
required
|
||||
min="1"
|
||||
max="2147483647"
|
||||
aria-label="Time to live in seconds"
|
||||
data-bind="
|
||||
css: {
|
||||
dirty: analyticalStorageTtlSelection.editableIsDirty
|
||||
},
|
||||
textInput: analyticalStorageTtlSeconds,
|
||||
enable: analyticalStorageTtlSelection() === 'on'"
|
||||
/>
|
||||
second(s)
|
||||
</div>
|
||||
</div>
|
||||
<div data-bind="visible: changeFeedPolicyVisible">
|
||||
<div class="formTitle">
|
||||
<span>Change feed log retention policy</span>
|
||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
||||
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
|
||||
<span class="tooltiptext infoTooltipWidth"
|
||||
>Enable change feed log retention policy to retain last 10 minutes of history for items in the container
|
||||
by default. To support this, the request unit (RU) charge for this container will be multiplied by a
|
||||
factor of two for writes. Reads are unaffected.</span
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<div class="tabs disableFocusDefaults" aria-label="Change feed selection tabs">
|
||||
<div class="tab">
|
||||
<label
|
||||
tabindex="0"
|
||||
data-bind="
|
||||
attr:{
|
||||
for: changeFeedPolicyOffId
|
||||
},
|
||||
css: {
|
||||
dirty: changeFeedPolicyToggled.editableIsDirty,
|
||||
selectedRadio: changeFeedPolicyToggled() === 'Off',
|
||||
unselectedRadio: changeFeedPolicyToggled() === 'On'
|
||||
},
|
||||
event: {
|
||||
keypress: onChangeFeedPolicyOffKeyPress
|
||||
}"
|
||||
>Off</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="changeFeedPolicy"
|
||||
value="Off"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: changeFeedPolicyOffId
|
||||
},
|
||||
checked: changeFeedPolicyToggled"
|
||||
/>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<label
|
||||
tabindex="0"
|
||||
data-bind="
|
||||
attr:{
|
||||
for: changeFeedPolicyOnId
|
||||
},
|
||||
css: {
|
||||
dirty: changeFeedPolicyToggled.editableIsDirty,
|
||||
selectedRadio: changeFeedPolicyToggled() === 'On',
|
||||
unselectedRadio: changeFeedPolicyToggled() === 'Off'
|
||||
},
|
||||
event: {
|
||||
keypress: onChangeFeedPolicyOnKeyPress
|
||||
}"
|
||||
>On</label
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="changeFeedPolicy"
|
||||
value="On"
|
||||
class="radio"
|
||||
data-bind="
|
||||
attr:{
|
||||
id: changeFeedPolicyOnId
|
||||
},
|
||||
checked: changeFeedPolicyToggled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-bind="visible: partitionKeyVisible">
|
||||
<div class="formTitle" data-bind="text: partitionKeyName">Partition Key</div>
|
||||
<input
|
||||
class="formReadOnly collid-white"
|
||||
data-bind="textInput: partitionKeyValue, attr: { 'aria-label':partitionKeyName }"
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
<div class="largePartitionKeyEnabled" data-bind="visible: isLargePartitionKeyEnabled">
|
||||
<p data-bind="visible: isLargePartitionKeyEnabled">
|
||||
Large <span data-bind="text:lowerCasePartitionKeyName"></span> has been enabled
|
||||
</p>
|
||||
</div>
|
||||
<div data-bind="visible: shouldShowIndexingPolicyEditor">
|
||||
<div class="formTitle">Indexing Policy</div>
|
||||
<div
|
||||
class="indexingPolicyEditor ttlIndexingPolicyFocusElement"
|
||||
tabindex="0"
|
||||
data-bind="setTemplateReady: true, attr:{ id: indexingPolicyEditorId }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,449 +0,0 @@
|
|||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Collection from "../Tree/Collection";
|
||||
import Database from "../Tree/Database";
|
||||
import Explorer from "../Explorer";
|
||||
import SettingsTab from "./SettingsTab";
|
||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||
import { IndexingPolicies } from "../../Shared/Constants";
|
||||
|
||||
describe("Settings tab", () => {
|
||||
const baseCollection: DataModels.Collection = {
|
||||
defaultTtl: 200,
|
||||
partitionKey: null,
|
||||
conflictResolutionPolicy: {
|
||||
mode: DataModels.ConflictResolutionMode.LastWriterWins,
|
||||
conflictResolutionPath: "/_ts"
|
||||
},
|
||||
indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
|
||||
_rid: "",
|
||||
_self: "",
|
||||
_etag: "",
|
||||
_ts: 0,
|
||||
id: "mycoll"
|
||||
};
|
||||
|
||||
const baseDatabase: DataModels.Database = {
|
||||
_rid: "",
|
||||
_self: "",
|
||||
_etag: "",
|
||||
_ts: 0,
|
||||
id: "mydb",
|
||||
collections: [baseCollection]
|
||||
};
|
||||
|
||||
const quotaInfo: DataModels.CollectionQuotaInfo = {
|
||||
storedProcedures: 0,
|
||||
triggers: 0,
|
||||
functions: 0,
|
||||
documentsSize: 0,
|
||||
documentsCount: 0,
|
||||
collectionSize: 0,
|
||||
usageSizeInKB: 0,
|
||||
numPartitions: 0
|
||||
};
|
||||
|
||||
describe("Conflict Resolution", () => {
|
||||
describe("should show conflict resolution", () => {
|
||||
let explorer: Explorer;
|
||||
const baseCollectionWithoutConflict: DataModels.Collection = {
|
||||
defaultTtl: 200,
|
||||
partitionKey: null,
|
||||
conflictResolutionPolicy: null,
|
||||
indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
|
||||
_rid: "",
|
||||
_self: "",
|
||||
_etag: "",
|
||||
_ts: 0,
|
||||
id: "mycoll"
|
||||
};
|
||||
const getSettingsTab = (conflictResolution: boolean = true) => {
|
||||
return new SettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||
title: "Scale & Settings",
|
||||
tabPath: "",
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(
|
||||
explorer,
|
||||
"mydb",
|
||||
conflictResolution ? baseCollection : baseCollectionWithoutConflict,
|
||||
quotaInfo,
|
||||
null
|
||||
),
|
||||
onUpdateTabsButtons: undefined
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
});
|
||||
|
||||
it("single master, should not show conflict resolution", () => {
|
||||
const settingsTab = getSettingsTab();
|
||||
expect(settingsTab.hasConflictResolution()).toBe(false);
|
||||
});
|
||||
|
||||
it("multi master with resolution conflict, show conflict resolution", () => {
|
||||
explorer.databaseAccount({
|
||||
id: "test",
|
||||
kind: "",
|
||||
location: "",
|
||||
name: "",
|
||||
tags: "",
|
||||
type: "",
|
||||
properties: {
|
||||
enableMultipleWriteLocations: true,
|
||||
documentEndpoint: "",
|
||||
cassandraEndpoint: "",
|
||||
gremlinEndpoint: "",
|
||||
tableEndpoint: ""
|
||||
}
|
||||
});
|
||||
|
||||
const settingsTab = getSettingsTab();
|
||||
expect(settingsTab.hasConflictResolution()).toBe(true);
|
||||
});
|
||||
|
||||
it("multi master without resolution conflict, show conflict resolution", () => {
|
||||
explorer.databaseAccount({
|
||||
id: "test",
|
||||
kind: "",
|
||||
location: "",
|
||||
name: "",
|
||||
tags: "",
|
||||
type: "",
|
||||
properties: {
|
||||
enableMultipleWriteLocations: true,
|
||||
documentEndpoint: "",
|
||||
cassandraEndpoint: "",
|
||||
gremlinEndpoint: "",
|
||||
tableEndpoint: ""
|
||||
}
|
||||
});
|
||||
|
||||
const settingsTab = getSettingsTab(false /* no resolution conflict*/);
|
||||
expect(settingsTab.hasConflictResolution()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse Conflict Resolution Mode from backend", () => {
|
||||
it("should parse any casing", () => {
|
||||
expect(SettingsTab.parseConflictResolutionMode("custom")).toBe(DataModels.ConflictResolutionMode.Custom);
|
||||
expect(SettingsTab.parseConflictResolutionMode("Custom")).toBe(DataModels.ConflictResolutionMode.Custom);
|
||||
expect(SettingsTab.parseConflictResolutionMode("lastWriterWins")).toBe(
|
||||
DataModels.ConflictResolutionMode.LastWriterWins
|
||||
);
|
||||
expect(SettingsTab.parseConflictResolutionMode("LastWriterWins")).toBe(
|
||||
DataModels.ConflictResolutionMode.LastWriterWins
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse empty as null", () => {
|
||||
expect(SettingsTab.parseConflictResolutionMode("")).toBe(null);
|
||||
});
|
||||
|
||||
it("should parse null as null", () => {
|
||||
expect(SettingsTab.parseConflictResolutionMode(null)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parse Conflict Resolution procedure from backend", () => {
|
||||
it("should parse path as name", () => {
|
||||
expect(SettingsTab.parseConflictResolutionProcedure("/dbs/xxxx/colls/xxxx/sprocs/validsproc")).toBe(
|
||||
"validsproc"
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse name as name", () => {
|
||||
expect(SettingsTab.parseConflictResolutionProcedure("validsproc")).toBe("validsproc");
|
||||
});
|
||||
|
||||
it("should parse invalid path as null", () => {
|
||||
expect(SettingsTab.parseConflictResolutionProcedure("/not/a/valid/path")).toBe(null);
|
||||
});
|
||||
|
||||
it("should parse empty or null as null", () => {
|
||||
expect(SettingsTab.parseConflictResolutionProcedure("")).toBe(null);
|
||||
expect(SettingsTab.parseConflictResolutionProcedure(null)).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Should update collection", () => {
|
||||
let explorer: Explorer;
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
});
|
||||
|
||||
it("On TTL changed", () => {
|
||||
const settingsTab = new SettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||
title: "Scale & Settings",
|
||||
tabPath: "",
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(false);
|
||||
settingsTab.timeToLive("off");
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(true);
|
||||
|
||||
settingsTab.onRevertClick();
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(false);
|
||||
settingsTab.timeToLiveSeconds(100);
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(true);
|
||||
});
|
||||
|
||||
it("On Index Policy changed", () => {
|
||||
const settingsTab = new SettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||
title: "Scale & Settings",
|
||||
tabPath: "",
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(false);
|
||||
settingsTab.indexingPolicyContent({ somethingDifferent: "" });
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(true);
|
||||
});
|
||||
|
||||
it("On Conflict Resolution Mode changed", () => {
|
||||
const settingsTab = new SettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||
title: "Scale & Settings",
|
||||
tabPath: "",
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(false);
|
||||
settingsTab.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.Custom);
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(true);
|
||||
|
||||
settingsTab.onRevertClick();
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(false);
|
||||
settingsTab.conflictResolutionPolicyPath("/somethingElse");
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(true);
|
||||
|
||||
settingsTab.onRevertClick();
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(false);
|
||||
settingsTab.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.Custom);
|
||||
settingsTab.conflictResolutionPolicyProcedure("resolver");
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Get Conflict Resolution configuration from user", () => {
|
||||
let explorer: Explorer;
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
});
|
||||
|
||||
it("null if it didnt change", () => {
|
||||
const settingsTab = new SettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||
title: "Scale & Settings",
|
||||
tabPath: "",
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
|
||||
});
|
||||
|
||||
it("Custom contains valid backend path", () => {
|
||||
const settingsTab = new SettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||
title: "Scale & Settings",
|
||||
tabPath: "",
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
|
||||
settingsTab.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.Custom);
|
||||
settingsTab.conflictResolutionPolicyProcedure("resolver");
|
||||
let updatedPolicy = settingsTab.getUpdatedConflictResolutionPolicy();
|
||||
expect(updatedPolicy.mode).toBe(DataModels.ConflictResolutionMode.Custom);
|
||||
expect(updatedPolicy.conflictResolutionProcedure).toBe("/dbs/mydb/colls/mycoll/sprocs/resolver");
|
||||
|
||||
settingsTab.conflictResolutionPolicyProcedure("");
|
||||
updatedPolicy = settingsTab.getUpdatedConflictResolutionPolicy();
|
||||
expect(updatedPolicy.conflictResolutionProcedure).toBe(undefined);
|
||||
});
|
||||
|
||||
it("LWW contains valid property path", () => {
|
||||
const settingsTab = new SettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||
title: "Scale & Settings",
|
||||
tabPath: "",
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
|
||||
settingsTab.conflictResolutionPolicyPath("someAttr");
|
||||
let updatedPolicy = settingsTab.getUpdatedConflictResolutionPolicy();
|
||||
expect(updatedPolicy.conflictResolutionPath).toBe("/someAttr");
|
||||
|
||||
settingsTab.conflictResolutionPolicyPath("/someAttr");
|
||||
updatedPolicy = settingsTab.getUpdatedConflictResolutionPolicy();
|
||||
expect(updatedPolicy.conflictResolutionPath).toBe("/someAttr");
|
||||
|
||||
settingsTab.conflictResolutionPolicyPath("");
|
||||
updatedPolicy = settingsTab.getUpdatedConflictResolutionPolicy();
|
||||
expect(updatedPolicy.conflictResolutionPath).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("partitionKeyVisible", () => {
|
||||
enum PartitionKeyOption {
|
||||
None,
|
||||
System,
|
||||
NonSystem
|
||||
}
|
||||
|
||||
function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) {
|
||||
const explorer = new Explorer();
|
||||
explorer.defaultExperience(defaultApi);
|
||||
|
||||
const offer: DataModels.Offer = null;
|
||||
const defaultTtl = 200;
|
||||
const conflictResolutionPolicy = {
|
||||
mode: DataModels.ConflictResolutionMode.LastWriterWins,
|
||||
conflictResolutionPath: "/_ts"
|
||||
};
|
||||
|
||||
return new Collection(
|
||||
explorer,
|
||||
"mydb",
|
||||
{
|
||||
defaultTtl: defaultTtl,
|
||||
partitionKey:
|
||||
partitionKeyOption != PartitionKeyOption.None
|
||||
? {
|
||||
paths: ["/foo"],
|
||||
kind: "Hash",
|
||||
version: 2,
|
||||
systemKey: partitionKeyOption === PartitionKeyOption.System
|
||||
}
|
||||
: null,
|
||||
conflictResolutionPolicy: conflictResolutionPolicy,
|
||||
indexingPolicy: IndexingPolicies.SharedDatabaseDefault,
|
||||
_rid: "",
|
||||
_self: "",
|
||||
_etag: "",
|
||||
_ts: 0,
|
||||
id: "mycoll"
|
||||
},
|
||||
quotaInfo,
|
||||
offer
|
||||
);
|
||||
}
|
||||
|
||||
function getSettingsTab(defaultApi: string, partitionKeyOption: PartitionKeyOption): SettingsTab {
|
||||
return new SettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Settings,
|
||||
title: "Scale & Settings",
|
||||
tabPath: "",
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: getCollection(defaultApi, partitionKeyOption),
|
||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
|
||||
});
|
||||
}
|
||||
|
||||
it("on SQL container with no partition key should be false", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.DocumentDB, PartitionKeyOption.None);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it("on Mongo container with no partition key should be false", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.MongoDB, PartitionKeyOption.None);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it("on Gremlin container with no partition key should be false", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Graph, PartitionKeyOption.None);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it("on Cassandra container with no partition key should be false", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Cassandra, PartitionKeyOption.None);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it("on Table container with no partition key should be false", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Table, PartitionKeyOption.None);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it("on SQL container with system partition key should be true", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.DocumentDB, PartitionKeyOption.System);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it("on Mongo container with system partition key should be false", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.MongoDB, PartitionKeyOption.System);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it("on Gremlin container with system partition key should be true", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Graph, PartitionKeyOption.System);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it("on Cassandra container with system partition key should be false", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Cassandra, PartitionKeyOption.System);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it("on Table container with system partition key should be false", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Table, PartitionKeyOption.System);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it("on SQL container with non-system partition key should be true", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.DocumentDB, PartitionKeyOption.NonSystem);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it("on Mongo container with non-system partition key should be true", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.MongoDB, PartitionKeyOption.NonSystem);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it("on Gremlin container with non-system partition key should be true", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Graph, PartitionKeyOption.NonSystem);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it("on Cassandra container with non-system partition key should be false", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Cassandra, PartitionKeyOption.NonSystem);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it("on Table container with non-system partition key should be false", () => {
|
||||
const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Table, PartitionKeyOption.NonSystem);
|
||||
expect(settingsTab.partitionKeyVisible()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -47,7 +47,7 @@ export default class SettingsTabV2 extends TabsBase {
|
|||
this.currentCollection.loadOffer().then(
|
||||
() => {
|
||||
// passed in options and set by parent as "Settings" by default
|
||||
this.tabTitle("Scale & Settings");
|
||||
this.tabTitle(this.currentCollection.offer() ? "Settings" : "Scale & Settings");
|
||||
this.offerRead(true);
|
||||
this.options.getPendingNotification.then(
|
||||
(data: DataModels.Notification) => {
|
||||
|
|
|
@ -9,7 +9,6 @@ import MongoQueryTabTemplate from "./MongoQueryTab.html";
|
|||
import MongoShellTabTemplate from "./MongoShellTab.html";
|
||||
import QueryTabTemplate from "./QueryTab.html";
|
||||
import QueryTablesTabTemplate from "./QueryTablesTab.html";
|
||||
import SettingsTabTemplate from "./SettingsTab.html";
|
||||
import SettingsTabV2Template from "./SettingsTabV2.html";
|
||||
import DatabaseSettingsTabTemplate from "./DatabaseSettingsTab.html";
|
||||
import StoredProcedureTabTemplate from "./StoredProcedureTab.html";
|
||||
|
@ -133,15 +132,6 @@ export class QueryTablesTab {
|
|||
}
|
||||
}
|
||||
|
||||
export class SettingsTab {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: TabComponent,
|
||||
template: SettingsTabTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsTabV2 {
|
||||
constructor() {
|
||||
return {
|
||||
|
|
|
@ -29,7 +29,6 @@ import MongoShellTab from "../Tabs/MongoShellTab";
|
|||
import QueryTab from "../Tabs/QueryTab";
|
||||
import QueryTablesTab from "../Tabs/QueryTablesTab";
|
||||
import SettingsTabV2 from "../Tabs/SettingsTabV2";
|
||||
import SettingsTab from "../Tabs/SettingsTab";
|
||||
import ConflictId from "./ConflictId";
|
||||
import DocumentId from "./DocumentId";
|
||||
import StoredProcedure from "./StoredProcedure";
|
||||
|
@ -556,11 +555,6 @@ export default class Collection implements ViewModels.Collection {
|
|||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
});
|
||||
|
||||
const isSettingsV2Enabled = this.container.isSettingsV2Enabled();
|
||||
if (!isSettingsV2Enabled) {
|
||||
await this.loadOffer();
|
||||
}
|
||||
|
||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => {
|
||||
|
@ -587,68 +581,8 @@ export default class Collection implements ViewModels.Collection {
|
|||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
};
|
||||
|
||||
if (isSettingsV2Enabled) {
|
||||
let settingsTabV2 = matchingTabs && (matchingTabs[0] as SettingsTabV2);
|
||||
this.launchSettingsTabV2(settingsTabV2, traceStartData, settingsTabOptions, pendingNotificationsPromise);
|
||||
} else {
|
||||
let settingsTab = matchingTabs && (matchingTabs[0] as SettingsTab);
|
||||
this.launchSettingsTabV1(settingsTab, traceStartData, settingsTabOptions, pendingNotificationsPromise);
|
||||
}
|
||||
};
|
||||
|
||||
private launchSettingsTabV1 = (
|
||||
settingsTab: SettingsTab,
|
||||
traceStartData: any,
|
||||
settingsTabOptions: ViewModels.TabOptions,
|
||||
getPendingNotification: Q.Promise<DataModels.Notification>
|
||||
): void => {
|
||||
if (!settingsTab) {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, traceStartData);
|
||||
settingsTabOptions.onLoadStartKey = startKey;
|
||||
|
||||
getPendingNotification.then(
|
||||
(data: any) => {
|
||||
const pendingNotification: DataModels.Notification = data && data[0];
|
||||
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.Settings;
|
||||
settingsTab = new SettingsTab(settingsTabOptions);
|
||||
this.container.tabsManager.activateNewTab(settingsTab);
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
},
|
||||
(error: any) => {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.Tab,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: settingsTabOptions.title,
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while fetching container settings for container ${this.id()}: ${errorMessage}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
getPendingNotification.then(
|
||||
(pendingNotification: DataModels.Notification) => {
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
this.container.tabsManager.activateTab(settingsTab);
|
||||
},
|
||||
(error: any) => {
|
||||
settingsTab.pendingNotification(undefined);
|
||||
this.container.tabsManager.activateTab(settingsTab);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private launchSettingsTabV2 = (
|
||||
|
|
Loading…
Reference in New Issue