Compare commits

...

18 Commits

Author SHA1 Message Date
Sung-Hyun Kang
5e8db9b539 Merge branch 'master' into missing_pk_fix 2025-03-24 10:41:14 -05:00
Sung-Hyun Kang
f94a452a98 Fix partition key missing not being able to load documents 2025-03-24 10:40:12 -05:00
tarazou9
6ce81099ef Handle catalog empty (#2082)
Handle UI errors caused by Catalog API calls returning no offering id.
2025-03-21 16:15:48 -04:00
Nishtha Ahuja
777e411f4f edited screenshot for vcore quickstart shell (#2080)
Co-authored-by: nishthaAhujaa <nishtha17354@iiittd.ac.in>
2025-03-20 21:55:03 +05:30
Laurent Nguyen
63d4b4f4ef fix tab wrapping with a lil' css tweak (#2013) (#2076)
Co-authored-by: Ashley Stanton-Nurse <ashleyst@microsoft.com>
2025-03-17 11:51:59 +01:00
asier-isayas
eaf9a14e7d Cancel Phoenix container allocation on ctrl+c & ctrl+z (#2055)
* Cancel Phoenix container allocation on ctrl+c

* revert package-lock

* fix build issues

* add ctrl+z

* Close terminal when Ctrl key is pressed

* format

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2025-03-13 14:56:11 -04:00
Sung-Hyun Kang
5997fabcda moving the throughput bucket flag to the client generation level 2025-02-11 15:03:23 -06:00
Sung-Hyun Kang
f01d4a5ae2 Merge branch 'master' into throughput_bucket 2025-02-07 09:49:10 -06:00
Sung-Hyun Kang
20eeed98e4 fix unit tests 2025-02-03 11:23:49 -06:00
Sung-Hyun Kang
ac53e1b3b5 Compile build fix 2025-02-03 10:53:44 -06:00
Sung-Hyun Kang
2cab086268 Edit package-lock 2025-02-03 10:49:53 -06:00
Sung-Hyun Kang
937451d844 Fixed unit tests 2025-02-02 22:18:02 -06:00
Sung-Hyun Kang
5dfaa9f0f8 Updated to a tab 2025-01-26 18:11:50 -06:00
Sung-Hyun Kang
05e2d0ac29 change query bucket to group 2025-01-21 10:17:17 -06:00
Sung-Hyun Kang
152c995ec0 Added logic 2025-01-21 09:28:49 -06:00
Sung-Hyun Kang
07c4ca9c50 enable/disable per autoscale selection 2025-01-13 09:49:48 -06:00
Sung-Hyun Kang
80781f7c8f fix bugs 2025-01-12 22:33:02 -06:00
Sung-Hyun Kang
aa39359460 Added throughput bucketing 2025-01-12 21:28:30 -06:00
14 changed files with 36627 additions and 1497 deletions

View File

@@ -1914,13 +1914,20 @@ input::-webkit-calendar-picker-indicator::after {
} }
.nav-tabs-margin { .nav-tabs-margin {
height: 32px;
background-color: #f2f2f2; background-color: #f2f2f2;
.nav-tabs { .nav-tabs {
display: flex; display: flex;
flex-wrap: wrap;
align-items: flex-end; align-items: flex-end;
height: 100%; height: 100%;
margin-bottom: -0.5px;
li {
// Override the bootstrap defaults here to align with our layout constants.
margin-bottom: 0px;
height: 32px;
}
} }
} }

51
package-lock.json generated
View File

@@ -86,7 +86,7 @@
"mkdirp": "1.0.4", "mkdirp": "1.0.4",
"monaco-editor": "0.44.0", "monaco-editor": "0.44.0",
"ms": "2.1.3", "ms": "2.1.3",
"p-retry": "4.6.2", "p-retry": "6.2.1",
"patch-package": "8.0.0", "patch-package": "8.0.0",
"plotly.js-cartesian-dist-min": "1.52.3", "plotly.js-cartesian-dist-min": "1.52.3",
"post-robot": "10.0.42", "post-robot": "10.0.42",
@@ -12662,7 +12662,9 @@
} }
}, },
"node_modules/@types/retry": { "node_modules/@types/retry": {
"version": "0.12.0", "version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
"integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/sanitize-html": { "node_modules/@types/sanitize-html": {
@@ -21799,6 +21801,18 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-network-error": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz",
"integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==",
"license": "MIT",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-number": { "node_modules/is-number": {
"version": "3.0.0", "version": "3.0.0",
"license": "MIT", "license": "MIT",
@@ -30243,14 +30257,20 @@
} }
}, },
"node_modules/p-retry": { "node_modules/p-retry": {
"version": "4.6.2", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz",
"integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/retry": "0.12.0", "@types/retry": "0.12.2",
"is-network-error": "^1.0.0",
"retry": "^0.13.1" "retry": "^0.13.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=16.17"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/p-try": { "node_modules/p-try": {
@@ -35997,6 +36017,13 @@
} }
} }
}, },
"node_modules/webpack-dev-server/node_modules/@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"dev": true,
"license": "MIT"
},
"node_modules/webpack-dev-server/node_modules/ajv": { "node_modules/webpack-dev-server/node_modules/ajv": {
"version": "8.12.0", "version": "8.12.0",
"dev": true, "dev": true,
@@ -36044,6 +36071,20 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/webpack-dev-server/node_modules/p-retry": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/retry": "0.12.0",
"retry": "^0.13.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/webpack-dev-server/node_modules/rimraf": { "node_modules/webpack-dev-server/node_modules/rimraf": {
"version": "3.0.2", "version": "3.0.2",
"dev": true, "dev": true,

View File

@@ -81,7 +81,7 @@
"mkdirp": "1.0.4", "mkdirp": "1.0.4",
"monaco-editor": "0.44.0", "monaco-editor": "0.44.0",
"ms": "2.1.3", "ms": "2.1.3",
"p-retry": "4.6.2", "p-retry": "6.2.1",
"patch-package": "8.0.0", "patch-package": "8.0.0",
"plotly.js-cartesian-dist-min": "1.52.3", "plotly.js-cartesian-dist-min": "1.52.3",
"post-robot": "10.0.42", "post-robot": "10.0.42",

37911
preview/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
* Notebook container related stuff * Notebook container related stuff
*/ */
import { useDialog } from "Explorer/Controls/Dialog"; import { useDialog } from "Explorer/Controls/Dialog";
import promiseRetry, { AbortError } from "p-retry"; import promiseRetry, { AbortError, Options } from "p-retry";
import { PhoenixClient } from "Phoenix/PhoenixClient"; import { PhoenixClient } from "Phoenix/PhoenixClient";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook, PoolIdType } from "../../Common/Constants"; import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook, PoolIdType } from "../../Common/Constants";
@@ -19,7 +19,7 @@ export class NotebookContainerClient {
private clearReconnectionAttemptMessage? = () => {}; private clearReconnectionAttemptMessage? = () => {};
private isResettingWorkspace: boolean; private isResettingWorkspace: boolean;
private phoenixClient: PhoenixClient; private phoenixClient: PhoenixClient;
private retryOptions: promiseRetry.Options; private retryOptions: Options;
private scheduleTimerId: NodeJS.Timeout; private scheduleTimerId: NodeJS.Timeout;
constructor(private onConnectionLost: () => void) { constructor(private onConnectionLost: () => void) {

View File

@@ -1028,6 +1028,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
); );
const selectedDocumentId = documentIds[clickedRowIndex as number]; const selectedDocumentId = documentIds[clickedRowIndex as number];
const originalPartitionKeyValue = selectedDocumentId.partitionKeyValue;
selectedDocumentId.partitionKeyValue = partitionKeyValueArray; selectedDocumentId.partitionKeyValue = partitionKeyValueArray;
onExecutionErrorChange(false); onExecutionErrorChange(false);
@@ -1063,9 +1064,14 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
setColumnDefinitionsFromDocument(documentContent); setColumnDefinitionsFromDocument(documentContent);
}, },
(error) => { (error) => {
// in case of any kind of failures of accidently changing partition key, restore the original
// so that when user navigates away from current document and comes back,
// it doesnt fail to load due to using the invalid partition keys
selectedDocumentId.partitionKeyValue = originalPartitionKeyValue;
onExecutionErrorChange(true); onExecutionErrorChange(true);
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
useDialog.getState().showOkModalDialog("Update document failed", errorMessage); useDialog.getState().showOkModalDialog("Update document failed", errorMessage);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.UpdateDocument, Action.UpdateDocument,
{ {

View File

@@ -5,6 +5,7 @@ import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
import * as ko from "knockout"; import * as ko from "knockout";
import * as React from "react"; import * as React from "react";
import FirewallRuleScreenshot from "../../../images/firewallRule.png"; import FirewallRuleScreenshot from "../../../images/firewallRule.png";
import VcoreFirewallRuleScreenshot from "../../../images/vcoreMongoFirewallRule.png";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -42,7 +43,11 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
return ( return (
<QuickstartFirewallNotification <QuickstartFirewallNotification
messageType={MessageTypes.OpenPostgresNetworkingBlade} messageType={MessageTypes.OpenPostgresNetworkingBlade}
screenshot={FirewallRuleScreenshot} screenshot={
this.kind === ViewModels.TerminalKind.Mongo || this.kind === ViewModels.TerminalKind.VCoreMongo
? VcoreFirewallRuleScreenshot
: FirewallRuleScreenshot
}
shellName={this.getShellNameForDisplay(this.kind)} shellName={this.getShellNameForDisplay(this.kind)}
/> />
); );

View File

@@ -4,7 +4,7 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils"; import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import promiseRetry, { AbortError } from "p-retry"; import promiseRetry, { AbortError, Options } from "p-retry";
import { import {
Areas, Areas,
ConnectionStatusType, ConnectionStatusType,
@@ -35,21 +35,26 @@ import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
export class PhoenixClient { export class PhoenixClient {
private armResourceId: string; private armResourceId: string;
private containerHealthHandler: NodeJS.Timeout; private containerHealthHandler: NodeJS.Timeout;
private retryOptions: promiseRetry.Options = { private retryOptions: Options = {
retries: Notebook.retryAttempts, retries: Notebook.retryAttempts,
maxTimeout: Notebook.retryAttemptDelayMs, maxTimeout: Notebook.retryAttemptDelayMs,
minTimeout: Notebook.retryAttemptDelayMs, minTimeout: Notebook.retryAttemptDelayMs,
}; };
private abortController: AbortController;
private abortSignal: AbortSignal;
constructor(armResourceId: string) { constructor(armResourceId: string) {
this.armResourceId = armResourceId; this.armResourceId = armResourceId;
} }
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> { public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
this.initializeCancelEventListener();
return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), { return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), {
retries: 4, retries: 4,
maxTimeout: 20000, maxTimeout: 20000,
minTimeout: 20000, minTimeout: 20000,
signal: this.abortSignal,
}); });
} }
@@ -270,6 +275,17 @@ export class PhoenixClient {
}; };
} }
private initializeCancelEventListener(): void {
this.abortController = new AbortController();
this.abortSignal = this.abortController.signal;
document.addEventListener("keydown", (event: KeyboardEvent) => {
if (event.ctrlKey && (event.key === "c" || event.key === "z")) {
this.abortController.abort(new AbortError("Request canceled"));
}
});
}
public ConvertToForbiddenErrorString(jsonData: IPhoenixError): string { public ConvertToForbiddenErrorString(jsonData: IPhoenixError): string {
const errInfo = jsonData; const errInfo = jsonData;
switch (errInfo?.type) { switch (errInfo?.type) {

View File

@@ -10,7 +10,7 @@ import {
Text, Text,
} from "@fluentui/react"; } from "@fluentui/react";
import { TFunction } from "i18next"; import { TFunction } from "i18next";
import promiseRetry, { AbortError } from "p-retry"; import promiseRetry, { AbortError, Options } from "p-retry";
import React from "react"; import React from "react";
import { WithTranslation } from "react-i18next"; import { WithTranslation } from "react-i18next";
import * as _ from "underscore"; import * as _ from "underscore";
@@ -80,7 +80,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
private static readonly defaultRetryIntervalInMs = 30000; private static readonly defaultRetryIntervalInMs = 30000;
private smartUiGeneratorClassName: string; private smartUiGeneratorClassName: string;
private retryIntervalInMs: number; private retryIntervalInMs: number;
private retryOptions: promiseRetry.Options; private retryOptions: Options;
private translationFunction: TFunction; private translationFunction: TFunction;
componentDidMount(): void { componentDidMount(): void {

View File

@@ -197,6 +197,11 @@ export const getPriceMapAndCurrencyCode = async (map: OfferingIdMap): Promise<Pr
const priceMap = new Map<string, Map<string, number>>(); const priceMap = new Map<string, Map<string, number>>();
let billingCurrency; let billingCurrency;
for (const region of map.keys()) { for (const region of map.keys()) {
// if no offering id is found for that region, skipping calling price API
const subMap = map.get(region);
if (!subMap || subMap.size === 0) {
continue;
}
const regionPriceMap = new Map<string, number>(); const regionPriceMap = new Map<string, number>();
const regionShortName = await getRegionShortName(region); const regionShortName = await getRegionShortName(region);
const requestBody: OfferingIdRequest = { const requestBody: OfferingIdRequest = {
@@ -237,7 +242,7 @@ export const getPriceMapAndCurrencyCode = async (map: OfferingIdMap): Promise<Pr
} catch (err) { } catch (err) {
const failureTelemetry = { err, selfServeClassName: SqlX.name }; const failureTelemetry = { err, selfServeClassName: SqlX.name };
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp); selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
return { priceMap: undefined, billingCurrency: undefined }; return { priceMap: new Map<string, Map<string, number>>(), billingCurrency: undefined };
} }
}; };
@@ -286,6 +291,6 @@ export const getOfferingIds = async (regions: Array<RegionItem>): Promise<Offeri
} catch (err) { } catch (err) {
const failureTelemetry = { err, selfServeClassName: SqlX.name }; const failureTelemetry = { err, selfServeClassName: SqlX.name };
selfServeTraceFailure(failureTelemetry, getOfferingIdsCodeTimestamp); selfServeTraceFailure(failureTelemetry, getOfferingIdsCodeTimestamp);
return undefined; return new Map<string, Map<string, string>>();
} }
}; };

View File

@@ -227,11 +227,13 @@ const calculateCost = (skuName: string, instanceCount: number): Description => {
let costPerHour = 0; let costPerHour = 0;
let costBreakdown = ""; let costBreakdown = "";
for (const regionItem of regions) { for (const regionItem of regions) {
const incrementalCost = priceMap.get(regionItem.locationName).get(skuName.replace("Cosmos.", "")); const incrementalCost = priceMap?.get(regionItem.locationName)?.get(skuName.replace("Cosmos.", ""));
if (incrementalCost === undefined) { if (incrementalCost === undefined) {
throw new Error(`${regionItem.locationName} not found in price map.`); throw new Error(`${regionItem.locationName} not found in price map.`);
} else if (incrementalCost === 0) { } else if (incrementalCost === 0) {
throw new Error(`${regionItem.locationName} cost per hour = 0`); throw new Error(`${regionItem.locationName} cost per hour = 0`);
} else if (currencyCode === undefined) {
throw new Error(`Currency code not found in price map.`);
} }
let regionalInstanceCount = instanceCount; let regionalInstanceCount = instanceCount;

View File

@@ -17,7 +17,7 @@ export class JupyterLabAppFactory {
if (userContext.apiType === "VCoreMongo" && content?.includes("MongoServerError: Invalid key")) { if (userContext.apiType === "VCoreMongo" && content?.includes("MongoServerError: Invalid key")) {
this.restartShell = true; this.restartShell = true;
} }
return content?.includes("cosmosuser@"); return content?.includes("cosmosshelluser@");
} }
private isMongoShellStarted(content: string | undefined) { private isMongoShellStarted(content: string | undefined) {
@@ -68,7 +68,6 @@ export class JupyterLabAppFactory {
const session = await manager.startNew(); const session = await manager.startNew();
session.messageReceived.connect(async (_, message: IMessage) => { session.messageReceived.connect(async (_, message: IMessage) => {
const content = message.content && message.content[0]?.toString(); const content = message.content && message.content[0]?.toString();
if (this.checkShellStarted && message.type == "stdout") { if (this.checkShellStarted && message.type == "stdout") {
//Close the terminal tab once the shell closed messages are received //Close the terminal tab once the shell closed messages are received
if (!this.isShellStarted) { if (!this.isShellStarted) {
@@ -114,6 +113,13 @@ export class JupyterLabAppFactory {
panel.dispose(); panel.dispose();
}); });
// Close terminal when Ctrl key is pressed
term.node.addEventListener("keydown", (event: KeyboardEvent) => {
if (event.ctrlKey) {
this.onShellExited(false);
}
});
return session; return session;
} }
} }

View File

@@ -35,6 +35,13 @@ describe("Query Utils", () => {
version: 2, version: 2,
}; };
}; };
const generatePartitionKeysForPaths = (paths: string[]): DataModels.PartitionKey => {
return {
paths: paths,
kind: "Hash",
version: 2,
};
};
describe("buildDocumentsQueryPartitionProjections()", () => { describe("buildDocumentsQueryPartitionProjections()", () => {
it("should return empty string if partition key is undefined", () => { it("should return empty string if partition key is undefined", () => {
@@ -89,6 +96,18 @@ describe("Query Utils", () => {
expect(query).toContain("c.id"); expect(query).toContain("c.id");
}); });
it("should always include {} for any missing partition keys", () => {
const query = QueryUtils.buildDocumentsQuery(
"",
["a", "b", "c"],
generatePartitionKeysForPaths(["/a", "/b", "/c"]),
[],
);
expect(query).toContain('IIF(IS_DEFINED(c["a"]), c["a"], {})');
expect(query).toContain('IIF(IS_DEFINED(c["b"]), c["b"], {})');
expect(query).toContain('IIF(IS_DEFINED(c["c"]), c["c"], {})');
});
}); });
describe("queryPagesUntilContentPresent()", () => { describe("queryPagesUntilContentPresent()", () => {
@@ -201,18 +220,6 @@ describe("Query Utils", () => {
expect(expectedPartitionKeyValues).toContain(documentContent["Category"]); expect(expectedPartitionKeyValues).toContain(documentContent["Category"]);
}); });
it("should extract no partition key values in the case nested partition key", () => {
const singlePartitionKeyDefinition: PartitionKeyDefinition = {
kind: PartitionKeyKind.Hash,
paths: ["/Location.type"],
};
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(
documentContent,
singlePartitionKeyDefinition,
);
expect(partitionKeyValues.length).toBe(0);
});
it("should extract all partition key values for hierarchical and nested partition keys", () => { it("should extract all partition key values for hierarchical and nested partition keys", () => {
const mixedPartitionKeyDefinition: PartitionKeyDefinition = { const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
kind: PartitionKeyKind.MultiHash, kind: PartitionKeyKind.MultiHash,
@@ -225,5 +232,52 @@ describe("Query Utils", () => {
expect(partitionKeyValues.length).toBe(2); expect(partitionKeyValues.length).toBe(2);
expect(partitionKeyValues).toEqual(["United States", "Point"]); expect(partitionKeyValues).toEqual(["United States", "Point"]);
}); });
it("if any partition key is null or empty string, the partitionKeyValues shall match", () => {
const newDocumentContent = {
...documentContent,
...{
Country: null,
Location: {
type: "",
coordinates: [-121.49, 46.206],
},
},
};
const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
kind: PartitionKeyKind.MultiHash,
paths: ["/Country", "/Location/type"],
};
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(
newDocumentContent,
mixedPartitionKeyDefinition,
);
expect(partitionKeyValues.length).toBe(2);
expect(partitionKeyValues).toEqual([null, ""]);
});
it("if any partition key doesn't exist, it should still set partitionkey value as {}", () => {
const newDocumentContent = {
...documentContent,
...{
Country: null,
Location: {
coordinates: [-121.49, 46.206],
},
},
};
const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
kind: PartitionKeyKind.MultiHash,
paths: ["/Country", "/Location/type"],
};
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(
newDocumentContent,
mixedPartitionKeyDefinition,
);
expect(partitionKeyValues.length).toBe(2);
expect(partitionKeyValues).toEqual([null, {}]);
});
}); });
}); });

View File

@@ -61,8 +61,9 @@ export function buildDocumentsQueryPartitionProjections(
projectedProperty += `[${projection}]`; projectedProperty += `[${projection}]`;
} }
}); });
const fullAccess = `${collectionAlias}${projectedProperty}`;
projections.push(`${collectionAlias}${projectedProperty}`); const wrappedProjection = `IIF(IS_DEFINED(${fullAccess}), ${fullAccess}, {})`;
projections.push(wrappedProjection);
} }
return projections.join(","); return projections.join(",");
@@ -130,6 +131,8 @@ export const extractPartitionKeyValues = (
if (value !== undefined) { if (value !== undefined) {
partitionKeyValues.push(value); partitionKeyValues.push(value);
} else {
partitionKeyValues.push({});
} }
}); });