Compare commits

...

14 Commits

Author SHA1 Message Date
Jordi Bunster
3dd25bdb4a Merge branch 'master' into jbunster/ru-gb-waiver 2020-10-21 13:21:13 -07:00
Steve Faulkner
e09730d782 Fixed Collections for Mongo can have 20gb (#293) 2020-10-20 22:58:36 -05:00
Chris-MS-896
09a95fded4 [ID: 833708][Screen Reader - CosmosDB – New KeySpace] Visual label(K… (#287)
* '[ID: 833708][Screen Reader - CosmosDB – New KeySpace] Visual label(Keyspace id) and aria-label(Database id) is not same for Keyspace id edit field.'

* 'update on aria-label'
2020-10-20 17:42:39 -05:00
Zachary Foster
7ffa18a190 Revert "Adds e2e tables test (#276)" (#292)
This reverts commit 30353c26f3.
2020-10-20 12:02:50 -05:00
Zachary Foster
30353c26f3 Adds e2e tables test (#276)
* Adds tables test

* Include .env var

* Adds asElement on again

* Add further loading states

* Format

* Hope to not lose focus

* Adds ID to shared key and modifies value of input directly

* Fix tables test

* Format

* Try uploading screenshots

* indent

* Fixes connection string

* Try wildcard upload path
2020-10-20 11:49:22 -04:00
Jordi Bunster
a293a87528 UI for RU/GB waiver preview feature 2020-10-20 01:37:39 -07:00
Srinath Narayanan
34d8704071 Added error field in traceFailure for SettingsV2 Component (#290)
* added error field in tracrFailure for SettingsV2 Component

* more edits

* minor edits
2020-10-19 17:12:01 -07:00
Steve Faulkner
23714831bd Fix getDataExplorerWindow (#285) 2020-10-16 16:01:41 -05:00
victor-meng
9a5d46b6e0 Move UDF and trigger operations to RP (#283)
Move UDF and trigger operations to RP
2020-10-16 19:58:45 +00:00
victor-meng
b9245101bc Fix two bugs with tables database offer (#282)
1. After moving read databases call to RP for Tables API, we lose the `_rid` property. Since we are reading Table's database offer with SDK, we need the `_rid` of the database to find its offer. Without `_rid`, we won't be able to find the database offer and the Scale tab would disappear again. The fix is to use SDK to read databases for Table API so we have `_rid`.
2. There's a bug in the non-aad code path for updating offers which prevents users from switching from manual to autoscale and vice versa. The fix is to properly set the option headers.
2020-10-16 19:24:45 +00:00
Steve Faulkner
274deb13be Update azure-pipelines.cg.yml for Azure Pipelines 2020-10-15 16:20:38 -05:00
victor-meng
9d50577800 Move stored procedure operations to RP (#281)
- move read, delete, create, and update stored procedure calls to RP
- fixed a bug where the console message never clears when reading offers with SDK
2020-10-15 18:10:20 +00:00
Tanuj Mittal
bd00e5eb9b Support async notebook publishing (#275)
Handle cases for async notebook publishing. Now `Your published work` tab shows 3 sections - published, under review, and removed notebooks.

Note: The text labels are design placeholders

![image](https://user-images.githubusercontent.com/693092/95799994-3b5fb100-0cab-11eb-86fc-4ded0aeeddb1.png)
2020-10-15 03:49:18 +00:00
Steve Faulkner
821f665e78 Remove window.dataExplorerPlatform (#279)
More cleanup for #253 
- Remove window.dataExplorerPlatform
- Remove explorer factories
2020-10-15 03:25:13 +00:00
62 changed files with 726 additions and 333 deletions

View File

@@ -3,6 +3,9 @@
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml
trigger:
- master
pool:
vmImage: "ubuntu-latest"

View File

@@ -112,7 +112,6 @@ describe("endpoint", () => {
describe("requestPlugin", () => {
beforeEach(() => {
delete window.dataExplorerPlatform;
resetConfigContext();
});

View File

@@ -1,28 +1,76 @@
import { AuthType } from "../../AuthType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import {
createUpdateSqlStoredProcedure,
getSqlStoredProcedure
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function createStoredProcedure(
databaseId: string,
collectionId: string,
storedProcedure: StoredProcedureDefinition
): Promise<StoredProcedureDefinition & Resource> {
let createdStoredProcedure: StoredProcedureDefinition & Resource;
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
try {
const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id
);
if (getResponse?.properties?.resource) {
throw new Error(
`Create stored procedure failed: stored procedure with id ${storedProcedure.id} already exists`
);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
properties: {
resource: storedProcedure as SqlStoredProcedureResource,
options: {}
}
};
const rpResponse = await createUpdateSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id,
createSprocParams
);
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedures.create(storedProcedure);
createdStoredProcedure = response.resource;
return response?.resource;
} catch (error) {
logConsoleError(`Error while creating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return createdStoredProcedure;
}

View File

@@ -1,28 +1,71 @@
import { AuthType } from "../../AuthType";
import { Resource, TriggerDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
SqlTriggerCreateUpdateParameters,
SqlTriggerResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function createTrigger(
databaseId: string,
collectionId: string,
trigger: TriggerDefinition
): Promise<TriggerDefinition & Resource> {
let createdTrigger: TriggerDefinition & Resource;
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
try {
const getResponse = await getSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id
);
if (getResponse?.properties?.resource) {
throw new Error(`Create trigger failed: trigger with id ${trigger.id} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
properties: {
resource: trigger as SqlTriggerResource,
options: {}
}
};
const rpResponse = await createUpdateSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id,
createTriggerParams
);
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.triggers.create(trigger);
createdTrigger = response.resource;
return response.resource;
} catch (error) {
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "CreateTrigger", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return createdTrigger;
}

View File

@@ -1,28 +1,76 @@
import { AuthType } from "../../AuthType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import {
createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function createUserDefinedFunction(
databaseId: string,
collectionId: string,
userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> {
let createdUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
try {
const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id
);
if (getResponse?.properties?.resource) {
throw new Error(
`Create user defined function failed: user defined function with id ${userDefinedFunction.id} already exists`
);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
properties: {
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
options: {}
}
};
const rpResponse = await createUpdateSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id,
createUDFParams
);
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunctions.create(userDefinedFunction);
createdUserDefinedFunction = response.resource;
return response?.resource;
} catch (error) {
logConsoleError(`Error while creating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "CreateUserupdateUserDefinedFunction", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return createdUserDefinedFunction;
}

View File

@@ -1,7 +1,10 @@
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function deleteStoredProcedure(
databaseId: string,
@@ -10,17 +13,28 @@ export async function deleteStoredProcedure(
): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
try {
await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedure(storedProcedureId)
.delete();
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedureId
);
} else {
await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedure(storedProcedureId)
.delete();
}
} catch (error) {
logConsoleError(`Error while deleting stored procedure ${storedProcedureId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return undefined;
}

View File

@@ -1,22 +1,36 @@
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
try {
await client()
.database(databaseId)
.container(collectionId)
.scripts.trigger(triggerId)
.delete();
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
triggerId
);
} else {
await client()
.database(databaseId)
.container(collectionId)
.scripts.trigger(triggerId)
.delete();
}
} catch (error) {
logConsoleError(`Error while deleting trigger ${triggerId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "DeleteTrigger", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return undefined;
}

View File

@@ -1,22 +1,36 @@
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
try {
await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunction(id)
.delete();
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
id
);
} else {
await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunction(id)
.delete();
}
} catch (error) {
logConsoleError(`Error while deleting user defined function ${id}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "DeleteUserDefinedFunction", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return undefined;
}

View File

@@ -12,14 +12,14 @@ import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function readDatabases(): Promise<DataModels.Database[]> {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
return [{ id: "TablesDB" } as DataModels.Database];
}
let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
databases = await readDatabasesWithARM();
} else {
const sdkResponse = await client()

View File

@@ -13,10 +13,13 @@ export const readOffers = async (): Promise<Offer[]> => {
const clearMessage = logConsoleProgress(`Querying offers`);
try {
if (configContext.platform === Platform.Portal) {
return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
const offers = sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
userContext.databaseAccount.id,
ClientDefaults.portalCacheTimeoutMs
]);
clearMessage();
return offers;
}
} catch (error) {
// If error getting cached Offers, continue on and read via SDK

View File

@@ -1,27 +1,41 @@
import { AuthType } from "../../AuthType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function readStoredProcedures(
databaseId: string,
collectionId: string
): Promise<(StoredProcedureDefinition & Resource)[]> {
let sprocs: (StoredProcedureDefinition & Resource)[];
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
const rpResponse = await listSqlStoredProcedures(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId
);
return rpResponse?.value?.map(sproc => sproc.properties?.resource as StoredProcedureDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedures.readAll()
.fetchAll();
sprocs = response.resources;
return response?.resources;
} catch (error) {
logConsoleError(`Failed to query stored procedures for container ${collectionId}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return sprocs;
}

View File

@@ -1,27 +1,41 @@
import { AuthType } from "../../AuthType";
import { Resource, TriggerDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function readTriggers(
databaseId: string,
collectionId: string
): Promise<(TriggerDefinition & Resource)[]> {
let triggers: (TriggerDefinition & Resource)[];
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
const rpResponse = await listSqlTriggers(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId
);
return rpResponse?.value?.map(trigger => trigger.properties?.resource as TriggerDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.triggers.readAll()
.fetchAll();
triggers = response.resources;
return response?.resources;
} catch (error) {
logConsoleError(`Failed to query triggers for container ${collectionId}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadTriggers", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return triggers;
}

View File

@@ -1,27 +1,41 @@
import { AuthType } from "../../AuthType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function readUserDefinedFunctions(
databaseId: string,
collectionId: string
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
let udfs: (UserDefinedFunctionDefinition & Resource)[];
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
const rpResponse = await listSqlUserDefinedFunctions(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId
);
return rpResponse?.value?.map(udf => udf.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunctions.readAll()
.fetchAll();
udfs = response.resources;
return response?.resources;
} catch (error) {
logConsoleError(`Failed to query user defined functions for container ${collectionId}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "ReadUserDefinedFunctions", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return udfs;
}

View File

@@ -406,10 +406,14 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
const options: RequestOptions = {};
if (params.migrateToAutoPilot) {
options.initialHeaders[HttpHeaders.migrateOfferToAutopilot] = "true";
options.initialHeaders = {
[HttpHeaders.migrateOfferToAutopilot]: "true"
};
delete newOffer.content.offerAutopilotSettings;
} else if (params.migrateToManual) {
options.initialHeaders[HttpHeaders.migrateOfferToManualThroughput] = "true";
options.initialHeaders = {
[HttpHeaders.migrateOfferToManualThroughput]: "true"
};
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
}

View File

@@ -1,29 +1,71 @@
import { AuthType } from "../../AuthType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import {
createUpdateSqlStoredProcedure,
getSqlStoredProcedure
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function updateStoredProcedure(
databaseId: string,
collectionId: string,
storedProcedure: StoredProcedureDefinition
): Promise<StoredProcedureDefinition & Resource> {
let updatedStoredProcedure: StoredProcedureDefinition & Resource;
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id
);
if (getResponse?.properties?.resource) {
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
properties: {
resource: storedProcedure as SqlStoredProcedureResource,
options: {}
}
};
const rpResponse = await createUpdateSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id,
createSprocParams
);
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
}
throw new Error(`Failed to update stored procedure: ${storedProcedure.id} does not exist.`);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedure(storedProcedure.id)
.replace(storedProcedure);
updatedStoredProcedure = response.resource;
return response?.resource;
} catch (error) {
logConsoleError(`Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
const errorMessage = error.code === "NotFound" ? `${storedProcedure.id} does not exist.` : JSON.stringify(error);
logConsoleError(`Error while updating stored procedure ${storedProcedure.id}:\n ${errorMessage}`);
logError(errorMessage, "UpdateStoredProcedure", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return updatedStoredProcedure;
}

View File

@@ -1,29 +1,68 @@
import { AuthType } from "../../AuthType";
import {
SqlTriggerCreateUpdateParameters,
SqlTriggerResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { TriggerDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function updateTrigger(
databaseId: string,
collectionId: string,
trigger: TriggerDefinition
): Promise<TriggerDefinition> {
let updatedTrigger: TriggerDefinition;
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
const getResponse = await getSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id
);
if (getResponse?.properties?.resource) {
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
properties: {
resource: trigger as SqlTriggerResource,
options: {}
}
};
const rpResponse = await createUpdateSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id,
createTriggerParams
);
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition);
}
throw new Error(`Failed to update trigger: ${trigger.id} does not exist.`);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.trigger(trigger.id)
.replace(trigger);
updatedTrigger = response.resource;
return response?.resource;
} catch (error) {
logConsoleError(`Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateTrigger", error.code);
const errorMessage = error.code === "NotFound" ? `${trigger.id} does not exist.` : JSON.stringify(error);
logConsoleError(`Error while updating trigger ${trigger.id}:\n ${errorMessage}`);
logError(errorMessage, "UpdateTrigger", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return updatedTrigger;
}

View File

@@ -1,29 +1,72 @@
import { AuthType } from "../../AuthType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import {
createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function updateUserDefinedFunction(
databaseId: string,
collectionId: string,
userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> {
let updatedUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id
);
if (getResponse?.properties?.resource) {
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
properties: {
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
options: {}
}
};
const rpResponse = await createUpdateSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id,
createUDFParams
);
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
throw new Error(`Failed to update user defined function: ${userDefinedFunction.id} does not exist.`);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunction(userDefinedFunction.id)
.replace(userDefinedFunction);
updatedUserDefinedFunction = response.resource;
return response?.resource;
} catch (error) {
logConsoleError(`Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateUserupdateUserDefinedFunction", error.code);
const errorMessage =
error.code === "NotFound" ? `${userDefinedFunction.id} does not exist.` : JSON.stringify(error);
logConsoleError(`Error while updating user defined function ${userDefinedFunction.id}:\n ${errorMessage}`);
logError(errorMessage, "UpdateUserupdateUserDefinedFunction", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
clearMessage();
return updatedUserDefinedFunction;
}

View File

@@ -18,7 +18,9 @@ describe("GalleryCardComponent", () => {
downloads: 0,
favorites: 0,
views: 0,
newCellId: undefined
newCellId: undefined,
policyViolations: undefined,
pendingScanJobIds: undefined
},
isFavorite: false,
showDownload: true,

View File

@@ -1,6 +1,7 @@
import {
Dropdown,
FocusZone,
FontWeights,
IDropdownOption,
IPageSpecification,
IPivotItemProps,
@@ -11,7 +12,8 @@ import {
Pivot,
PivotItem,
SearchBox,
Stack
Stack,
Text
} from "office-ui-fabric-react";
import * as React from "react";
import * as Logger from "../../../Common/Logger";
@@ -151,7 +153,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
// Displaying code of conduct component on gallery load should not be the default behavior.
if (this.state.isCodeOfConductAccepted !== false) {
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
}
}
@@ -197,10 +199,59 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
return {
tab,
content: this.createTabContent(data)
content: this.createSearchBarHeader(this.createCardsTabContent(data))
};
}
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
return {
tab,
content: this.createPublishedNotebooksTabContent(data)
};
};
private createPublishedNotebooksTabContent = (data: IGalleryItem[]): JSX.Element => {
const { published, underReview, removed } = GalleryUtils.filterPublishedNotebooks(data);
const content = (
<Stack tokens={{ childrenGap: 10 }}>
{published?.length > 0 &&
this.createPublishedNotebooksSectionContent(
undefined,
"You have successfully published the following notebook(s) to public gallery and shared with other Azure Cosmos DB users.",
this.createCardsTabContent(published)
)}
{underReview?.length > 0 &&
this.createPublishedNotebooksSectionContent(
"Under Review",
"Content of a notebook you published is currently being scanned for illegal content. It will not be available to public gallery until the review is completed (may take a few days)",
this.createCardsTabContent(underReview)
)}
{removed?.length > 0 &&
this.createPublishedNotebooksSectionContent(
"Removed",
"These notebooks were found to contain illegal content and has been taken down.",
this.createPolicyViolationsListContent(removed)
)}
</Stack>
);
return this.createSearchBarHeader(content);
};
private createPublishedNotebooksSectionContent = (
title: string,
description: string,
content: JSX.Element
): JSX.Element => {
return (
<Stack tokens={{ childrenGap: 5 }}>
{title && <Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{title}</Text>}
{description && <Text>{description}</Text>}
{content}
</Stack>
);
};
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
return acceptedCodeOfConduct === false ? (
<CodeOfConductComponent
@@ -210,11 +261,11 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}}
/>
) : (
this.createTabContent(data)
this.createSearchBarHeader(this.createCardsTabContent(data))
);
}
private createTabContent(data: IGalleryItem[]): JSX.Element {
private createSearchBarHeader(content: JSX.Element): JSX.Element {
return (
<Stack tokens={{ childrenGap: 10 }}>
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
@@ -233,7 +284,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
</Stack.Item>
)}
</Stack>
{data && this.createCardsTabContent(data)}
<Stack.Item>{content}</Stack.Item>
</Stack>
);
}
@@ -251,6 +302,25 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
);
}
private createPolicyViolationsListContent(data: IGalleryItem[]): JSX.Element {
return (
<table>
<tbody>
<tr>
<th>Name</th>
<th>Policy violations</th>
</tr>
{data.map(item => (
<tr key={`policy-violations-tr-${item.id}`}>
<td>{item.name}</td>
<td>{item.policyViolations.join(", ")}</td>
</tr>
))}
</tbody>
</table>
);
}
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
switch (tab) {
case GalleryTab.OfficialSamples:

View File

@@ -29,7 +29,7 @@ export class InfoComponent extends React.Component<InfoComponentProps> {
<Stack.Item>
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
</Stack.Item>
{this.props.onReportAbuseClick !== undefined && (
{this.props.onReportAbuseClick && (
<Stack.Item>
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
</Stack.Item>

View File

@@ -81,6 +81,21 @@ exports[`GalleryViewerComponent renders 1`] = `
<InfoComponent />
</StackItem>
</Stack>
<StackItem>
<FocusZone
direction={2}
isCircularNavigation={false}
shouldRaiseClicks={true}
>
<List
getPageSpecification={[Function]}
onRenderCell={[Function]}
renderedWindowsAhead={3}
renderedWindowsBehind={2}
startIndex={0}
/>
</FocusZone>
</StackItem>
</Stack>
</PivotItem>
</StyledPivotBase>

View File

@@ -18,7 +18,9 @@ describe("NotebookMetadataComponent", () => {
downloads: 0,
favorites: 0,
views: 0,
newCellId: undefined
newCellId: undefined,
policyViolations: undefined,
pendingScanJobIds: undefined
},
isFavorite: false,
downloadButtonText: "Download",
@@ -48,7 +50,9 @@ describe("NotebookMetadataComponent", () => {
downloads: 0,
favorites: 0,
views: 0,
newCellId: undefined
newCellId: undefined,
policyViolations: undefined,
pendingScanJobIds: undefined
},
isFavorite: true,
downloadButtonText: "Download",

View File

@@ -408,8 +408,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
Action.SettingsV2Updated,
{
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.collection && this.collection.databaseId,
collectionName: this.collection && this.collection.id(),
databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(),
@@ -457,6 +457,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
Action.SettingsV2Updated,
{
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle()
@@ -471,9 +473,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
Action.SettingsV2Updated,
{
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle()
tabTitle: this.props.settingsTab.tabTitle(),
error: reason
},
startKey
);

View File

@@ -5,7 +5,6 @@ import { container, collection } from "../TestUtils";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
import Explorer from "../../../Explorer";
import * as Constants from "../../../../Common/Constants";
import { PlatformType } from "../../../../PlatformType";
import * as DataModels from "../../../../Contracts/DataModels";
import { throughputUnit } from "../SettingsRenderUtils";
import * as SharedConstants from "../../../../Shared/Constants";
@@ -13,7 +12,6 @@ import ko from "knockout";
describe("ScaleComponent", () => {
const nonNationalCloudContainer = new Explorer();
nonNationalCloudContainer.getPlatformType = () => PlatformType.Portal;
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
const targetThroughput = 6000;
@@ -119,7 +117,7 @@ describe("ScaleComponent", () => {
it("getThroughputTitle", () => {
let scaleComponent = new ScaleComponent(baseProps);
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - 40,000 RU/s)");
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
let newProps = { ...baseProps, container: nonNationalCloudContainer };
scaleComponent = new ScaleComponent(newProps);
@@ -132,7 +130,7 @@ describe("ScaleComponent", () => {
it("canThroughputExceedMaximumValue", () => {
let scaleComponent = new ScaleComponent(baseProps);
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(false);
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
const newProps = { ...baseProps, container: nonNationalCloudContainer };
scaleComponent = new ScaleComponent(newProps);

View File

@@ -5,7 +5,6 @@ import * as ViewModels from "../../../../Contracts/ViewModels";
import * as DataModels from "../../../../Contracts/DataModels";
import * as SharedConstants from "../../../../Shared/Constants";
import Explorer from "../../../Explorer";
import { PlatformType } from "../../../../PlatformType";
import {
getTextFieldStyles,
subComponentStackProps,
@@ -78,7 +77,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
};
public getMaxRUThroughputInputLimit = (): number => {
if (this.props.container?.getPlatformType() === PlatformType.Hosted && this.props.collection.partitionKey) {
if (configContext.platform === Platform.Hosted && this.props.collection.partitionKey) {
return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
}
@@ -100,8 +99,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
public canThroughputExceedMaximumValue = (): boolean => {
const isPublicAzurePortal: boolean =
this.props.container.getPlatformType() === PlatformType.Portal &&
!this.props.container.isRunningOnNationalCloud();
configContext.platform === Platform.Portal && !this.props.container.isRunningOnNationalCloud();
const hasPartitionKey = !!this.props.collection.partitionKey;
return isPublicAzurePortal && hasPartitionKey;

View File

@@ -39,13 +39,13 @@ exports[`ScaleComponent renders with correct intiial notification 1`] = `
}
>
<ThroughputInputAutoPilotV3Component
canExceedMaximumValue={false}
canExceedMaximumValue={true}
getThroughputWarningMessage={[Function]}
isAutoPilotSelected={false}
isEmulator={false}
isEnabled={true}
isFixed={false}
label="Throughput (6,000 - 40,000 RU/s)"
label="Throughput (6,000 - unlimited RU/s)"
maxAutoPilotThroughput={4000}
maxAutoPilotThroughputBaseline={4000}
maximum={40000}

View File

@@ -45,7 +45,7 @@
<input
class="throughputModeRadio nonFirstRadio"
aria-label="Provisioned Throughput mode"
aria-label="Manual mode"
type="radio"
role="radio"
tabindex="0"

View File

@@ -44,7 +44,7 @@
<input
class="throughputModeRadio nonFirstRadio"
aria-label="Provisioned Throughput mode"
aria-label="Manual mode"
type="radio"
role="radio"
tabindex="0"

View File

@@ -37,7 +37,7 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { configContext, updateConfigContext } from "../ConfigContext";
import { configContext, Platform, updateConfigContext } from "../ConfigContext";
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
@@ -58,7 +58,6 @@ import { NotebookUtil } from "./Notebook/NotebookUtil";
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
import { NotificationConsoleComponentAdapter } from "./Menus/NotificationConsole/NotificationConsoleComponentAdapter";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { PlatformType } from "../PlatformType";
import { QueriesClient } from "../Common/QueriesClient";
import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane";
import { RenewAdHocAccessPane } from "./Panes/RenewAdHocAccessPane";
@@ -565,9 +564,7 @@ export default class Explorer {
this.isHostedDataExplorerEnabled = ko.computed<boolean>(
() =>
this.getPlatformType() === PlatformType.Portal &&
!this.isRunningOnNationalCloud() &&
!this.isPreferredApiGraph()
configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph()
);
this.isRightPanelV2Enabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableRightPanelV2)
@@ -1793,7 +1790,7 @@ export default class Explorer {
const message: any = event.data.data;
const inputs: ViewModels.DataExplorerInputsFrame = message.inputs;
const isRunningInPortal = window.dataExplorerPlatform == PlatformType.Portal;
const isRunningInPortal = configContext.platform === Platform.Portal;
const isRunningInDevMode = process.env.NODE_ENV === "development";
if (inputs && configContext.BACKEND_ENDPOINT && isRunningInPortal && isRunningInDevMode) {
inputs.extensionEndpoint = configContext.PROXY_PATH;
@@ -2009,10 +2006,6 @@ export default class Explorer {
this._panes.forEach((pane: ContextualPaneBase) => pane.close());
}
public getPlatformType(): PlatformType {
return window.dataExplorerPlatform;
}
public isRunningOnNationalCloud(): boolean {
return (
this.serverId() === Constants.ServerIds.blackforest ||

View File

@@ -1,5 +1,4 @@
import * as ViewModels from "../../../Contracts/ViewModels";
import { PlatformType } from "../../../PlatformType";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { Areas } from "../../../Common/Constants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -159,7 +158,7 @@ export class CommandBarComponentButtonFactory {
public static createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
if (window.dataExplorerPlatform === PlatformType.Hosted) {
if (configContext.platform === Platform.Hosted) {
return buttons;
}

View File

@@ -276,7 +276,7 @@
<!-- Fixed option button - Start -->
<div class="tab">
<input type="radio" id="tab1" name="storage" value="10" class="radio" data-bind="checked: storage">
<label for="tab1">Fixed (10 GB)</label>
<label for="tab1">Fixed (20 GB)</label>
</div>
<!-- Fixed option button - End -->

View File

@@ -14,8 +14,6 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import { configContext, Platform } from "../../ConfigContext";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { HashMap } from "../../Common/HashMap";
import { PlatformType } from "../../PlatformType";
import { refreshCachedResources } from "../../Common/DocumentClientUtilityBase";
import { createCollection } from "../../Common/dataAccess/createCollection";
@@ -327,7 +325,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
if (
configContext.platform !== Platform.Emulator &&
!this.container.isTryCosmosDBSubscription() &&
this.container.getPlatformType() !== PlatformType.Portal
configContext.platform !== Platform.Portal
) {
const offerThroughput: number = this._getThroughput();
return offerThroughput <= 100000;

View File

@@ -74,8 +74,8 @@
<input id="database-id" type="text" aria-required="true" autocomplete="off" pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
size="40" class="collid" data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus, attr: { placeholder: databaseIdPlaceHolder }"
aria-label="Database id" autofocus>
size="40" class="collid" data-bind="textInput: databaseId, hasFocus: firstFieldHasFocus, attr: { 'aria-label': databaseIdLabel, 'placeholder': databaseIdPlaceHolder }"
autofocus>
<!-- Database provisioned throughput - Start -->
<!-- ko if: canConfigureThroughput -->

View File

@@ -11,7 +11,6 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { createDatabase } from "../../Common/dataAccess/createDatabase";
import { PlatformType } from "../../PlatformType";
import { configContext, Platform } from "../../ConfigContext";
export default class AddDatabasePane extends ContextualPaneBase {
@@ -183,7 +182,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
if (
configContext.platform !== Platform.Emulator &&
!this.container.isTryCosmosDBSubscription() &&
this.container.getPlatformType() !== PlatformType.Portal
configContext.platform !== Platform.Portal
) {
const offerThroughput: number = this.throughput();
return offerThroughput <= 100000;

View File

@@ -140,10 +140,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
}
public async submit(): Promise<void> {
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Publishing ${this.name} to gallery`
);
const clearPublishingMessage = NotificationConsoleUtils.logConsoleProgress(`Publishing ${this.name} to gallery`);
this.isExecuting = true;
this.triggerRender();
@@ -161,8 +158,16 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
this.content,
this.isLinkInjectionEnabled
);
if (response.data) {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Published ${name} to gallery`);
const data = response.data;
if (data) {
if (data.pendingScanJobIds?.length > 0) {
NotificationConsoleUtils.logConsoleInfo(
`Content of ${this.name} is currently being scanned for illegal content. It will not be available in the public gallery until the review is complete (may take a few days).`
);
} else {
NotificationConsoleUtils.logConsoleInfo(`Published ${this.name} to gallery`);
}
}
} catch (error) {
this.formError = `Failed to publish ${this.name} to gallery`;
@@ -170,10 +175,10 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
const message = `${this.formError}: ${this.formErrorDetail}`;
Logger.logError(message, "PublishNotebookPaneAdapter/submit");
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
NotificationConsoleUtils.logConsoleError(message);
return;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
clearPublishingMessage();
this.isExecuting = false;
this.triggerRender();
}

View File

@@ -296,7 +296,9 @@ export class PublishNotebookPaneComponent extends React.Component<PublishNoteboo
downloads: 0,
favorites: 0,
views: 0,
newCellId: undefined
newCellId: undefined,
policyViolations: undefined,
pendingScanJobIds: undefined
}}
isFavorite={false}
showDownload={true}

View File

@@ -103,6 +103,8 @@ exports[`PublishNotebookPaneComponent renders 1`] = `
"isSample": false,
"name": "SampleNotebook.ipynb",
"newCellId": undefined,
"pendingScanJobIds": undefined,
"policyViolations": undefined,
"tags": Array [
"",
],

View File

@@ -85,6 +85,13 @@
<span>Learn more about minimum throughput </span>
<a href="https://docs.microsoft.com/azure/cosmos-db/set-throughput" target="_blank">here.</a>
</p>
<p data-bind="visible: canRequestBelowMinRU">
Need to scale below <span data-bind="text: minRUsText"></span> RU/s? Reach out by filling
<a
href="https://customervoice.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbRzBPrdEMjvxPuDm8fCLUtXpUQ0JLV1Y5VVlDS1RNUE1aRzVHQlVQTVA1SS4u"
>this questionnaire</a
>.
</p>
<p data-bind="visible: canRequestSupport">
<!-- TODO: Replace link with call to the Azure Support blade -->
<a href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20More%20Throughput%20Request"

View File

@@ -13,7 +13,6 @@ import SaveIcon from "../../../images/save-cosmos.svg";
import TabsBase from "./TabsBase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { PlatformType } from "../../PlatformType";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../Explorer";
import { updateOffer } from "../../Common/dataAccess/updateOffer";
@@ -62,6 +61,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
public saveSettingsButton: ViewModels.Button;
public discardSettingsChangesButton: ViewModels.Button;
public canRequestBelowMinRU: ko.PureComputed<boolean>;
public canRequestSupport: ko.PureComputed<boolean>;
public canThroughputExceedMaximumValue: ko.Computed<boolean>;
public costsVisible: ko.Computed<boolean>;
@@ -69,6 +69,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
public isTemplateReady: ko.Observable<boolean>;
public minRUAnotationVisible: ko.Computed<boolean>;
public minRUs: ko.Computed<number>;
public minRUsText: ko.PureComputed<string>;
public maxRUs: ko.Computed<number>;
public maxRUsText: ko.PureComputed<string>;
public maxRUThroughputInputLimit: ko.Computed<number>;
@@ -200,16 +201,19 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
return configContext.platform !== Platform.Emulator;
});
this.shouldDisplayPortalUsePrompt = ko.pureComputed<boolean>(
() => this.container.getPlatformType() === PlatformType.Hosted
);
this.shouldDisplayPortalUsePrompt = ko.pureComputed<boolean>(() => configContext.platform === Platform.Hosted);
this.canThroughputExceedMaximumValue = ko.pureComputed<boolean>(
() => this.container.getPlatformType() === PlatformType.Portal && !this.container.isRunningOnNationalCloud()
() => configContext.platform === Platform.Portal && !this.container.isRunningOnNationalCloud()
);
this.canRequestBelowMinRU = ko.pureComputed(() => {
return true; // TODO: Implement and test
});
this.canRequestSupport = ko.pureComputed(() => {
if (
configContext.platform === Platform.Emulator ||
this.container.getPlatformType() === PlatformType.Hosted ||
configContext.platform === Platform.Hosted ||
this.canThroughputExceedMaximumValue()
) {
return false;
@@ -273,13 +277,15 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
});
this.maxRUThroughputInputLimit = ko.pureComputed<number>(() => {
if (this.container && this.container.getPlatformType() === PlatformType.Hosted) {
if (configContext.platform === Platform.Hosted) {
return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
}
return this.maxRUs();
});
this.minRUsText = ko.pureComputed(() => this.minRUs().toLocaleString());
this.maxRUsText = ko.pureComputed(() => {
return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million.toLocaleString();
});

View File

@@ -10,10 +10,9 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { HashMap } from "../../Common/HashMap";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import { PlatformType } from "../../PlatformType";
import Explorer from "../Explorer";
import { userContext } from "../../UserContext";
import { configContext } from "../../ConfigContext";
import { configContext, Platform } from "../../ConfigContext";
export default class MongoShellTab extends TabsBase {
public url: ko.Computed<string>;
@@ -31,7 +30,7 @@ export default class MongoShellTab extends TabsBase {
const accountName = account && account.name;
const mongoEndpoint = account && (account.properties.mongoEndpoint || account.properties.documentEndpoint);
this._runtimeEndpoint = window.dataExplorerPlatform === PlatformType.Hosted ? configContext.BACKEND_ENDPOINT : "";
this._runtimeEndpoint = configContext.platform === Platform.Hosted ? configContext.BACKEND_ENDPOINT : "";
const extensionEndpoint: string = configContext.BACKEND_ENDPOINT || this._runtimeEndpoint || "";
let baseUrl = "/content/mongoshell/dist/";
if (this._container.serverId() === "localhost") {

View File

@@ -14,7 +14,6 @@ import SaveIcon from "../../../images/save-cosmos.svg";
import TabsBase from "./TabsBase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { PlatformType } from "../../PlatformType";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../Explorer";
import { updateOffer } from "../../Common/dataAccess/updateOffer";
@@ -494,7 +493,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.canThroughputExceedMaximumValue = ko.pureComputed<boolean>(() => {
const isPublicAzurePortal: boolean =
this.container.getPlatformType() === PlatformType.Portal && !this.container.isRunningOnNationalCloud();
configContext.platform === Platform.Portal && !this.container.isRunningOnNationalCloud();
const hasPartitionKey = !!this.collection.partitionKey;
return isPublicAzurePortal && hasPartitionKey;
@@ -513,7 +512,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
return false;
}
if (this.container.getPlatformType() === PlatformType.Hosted) {
if (configContext.platform === Platform.Hosted) {
return false;
}
@@ -526,7 +525,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
});
this.shouldDisplayPortalUsePrompt = ko.pureComputed<boolean>(
() => this.container.getPlatformType() === PlatformType.Hosted && !!this.collection.partitionKey
() => configContext.platform === Platform.Hosted && !!this.collection.partitionKey
);
this.minRUs = ko.computed<number>(() => {
@@ -597,7 +596,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
});
this.maxRUThroughputInputLimit = ko.pureComputed<number>(() => {
if (this.container && this.container.getPlatformType() === PlatformType.Hosted && this.collection.partitionKey) {
if (configContext.platform === Platform.Hosted && this.collection.partitionKey) {
return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
}

View File

@@ -14,7 +14,6 @@ import { readCollectionQuotaInfo } from "../../Common/dataAccess/readCollectionQ
import * as Logger from "../../Common/Logger";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { PlatformType } from "../../PlatformType";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
@@ -36,7 +35,7 @@ import DocumentId from "./DocumentId";
import StoredProcedure from "./StoredProcedure";
import Trigger from "./Trigger";
import UserDefinedFunction from "./UserDefinedFunction";
import { configContext } from "../../ConfigContext";
import { configContext, Platform } from "../../ConfigContext";
import Explorer from "../Explorer";
import { userContext } from "../../UserContext";
import TabsBase from "../Tabs/TabsBase";
@@ -1030,9 +1029,8 @@ export default class Collection implements ViewModels.Collection {
}
public uploadFiles = (fileList: FileList): Q.Promise<UploadDetails> => {
const platformType: string = PlatformType[(<any>window).dataExplorerPlatform];
// TODO: right now web worker is not working with AAD flow. Use main thread for upload for now until we have backend upload capability
if (platformType === PlatformType[PlatformType.Hosted] && window.authType === AuthType.AAD) {
if (configContext.platform === Platform.Hosted && window.authType === AuthType.AAD) {
return this._uploadFilesCors(fileList);
}
const documentUploader: Worker = new UploadWorker();

View File

@@ -56,7 +56,7 @@ export default class UserDefinedFunction {
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.UserDefinedFunctions,
tab => tab.collection && tab.collection.rid === this.rid
tab => tab.node?.rid === this.rid
) as UserDefinedFunctionTab[];
let userDefinedFunctionTab: UserDefinedFunctionTab = userDefinedFunctionTabs && userDefinedFunctionTabs[0];

View File

@@ -1,6 +1,6 @@
import ko from "knockout";
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
import { IPinnedRepo, JunoClient, IGalleryItem } from "./JunoClient";
import { IPinnedRepo, JunoClient } from "./JunoClient";
import { configContext } from "../ConfigContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { DatabaseAccount } from "../Contracts/DataModels";
@@ -33,24 +33,6 @@ const samplePinnedRepos: IPinnedRepo[] = [
}
];
const sampleGalleryItems: IGalleryItem[] = [
{
id: "id",
name: "name",
description: "description",
gitSha: "gitSha",
tags: ["tag1"],
author: "author",
thumbnailUrl: "thumbnailUrl",
created: "created",
isSample: false,
downloads: 0,
favorites: 0,
views: 0,
newCellId: undefined
}
];
describe("Pinned repos", () => {
const junoClient = new JunoClient(ko.observable<DatabaseAccount>(sampleDatabaseAccount));

View File

@@ -37,6 +37,8 @@ export interface IGalleryItem {
favorites: number;
views: number;
newCellId: string;
policyViolations: string[];
pendingScanJobIds: string[];
}
export interface IPublicGalleryData {

View File

@@ -65,7 +65,6 @@ import { BindingHandlersRegisterer } from "./Bindings/BindingHandlersRegisterer"
import * as Emulator from "./Platform/Emulator/Main";
import Hosted from "./Platform/Hosted/Main";
import * as Portal from "./Platform/Portal/Main";
import { PlatformType } from "./PlatformType";
import { AuthType } from "./AuthType";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
@@ -81,8 +80,6 @@ window.authType = AuthType.AAD;
initializeConfiguration().then(config => {
if (config.platform === Platform.Hosted) {
try {
// TODO Remove. All window variables should move to src/Config file
window.dataExplorerPlatform = PlatformType.Hosted;
Hosted.initializeExplorer().then(
(explorer: Explorer) => {
applyExplorerBindings(explorer);
@@ -108,14 +105,10 @@ initializeConfiguration().then(config => {
console.log(e);
}
} else if (config.platform === Platform.Emulator) {
// TODO Remove. All window variables should move to src/Config file
window.dataExplorerPlatform = PlatformType.Emulator;
window.authType = AuthType.MasterKey;
const explorer = Emulator.initializeExplorer();
applyExplorerBindings(explorer);
} else if (config.platform === Platform.Portal) {
// TODO Remove. All window variables should move to src/Config file
window.dataExplorerPlatform = PlatformType.Portal;
TelemetryProcessor.trace(Action.InitializeDataExplorer, ActionModifiers.Open, {});
const explorer = Portal.initializeExplorer();
TelemetryProcessor.trace(Action.InitializeDataExplorer, ActionModifiers.IFrameReady, {});

View File

@@ -1,27 +0,0 @@
import { AccountKind, TagNames, DefaultAccountExperience } from "../../Common/Constants";
import Explorer from "../../Explorer/Explorer";
export default class EmulatorExplorerFactory {
public static createExplorer(): Explorer {
const explorer: Explorer = new Explorer();
explorer.databaseAccount({
name: "",
id: "",
location: "",
type: "",
kind: AccountKind.DocumentDB,
tags: {
[TagNames.defaultExperience]: DefaultAccountExperience.DocumentDB
},
properties: {
documentEndpoint: "",
tableEndpoint: "",
gremlinEndpoint: "",
cassandraEndpoint: ""
}
});
explorer.isAccountReady(true);
return explorer;
}
}

View File

@@ -1,6 +1,24 @@
import EmulatorExplorerFactory from "./ExplorerFactory";
import Explorer from "../../Explorer/Explorer";
import { AccountKind, DefaultAccountExperience, TagNames } from "../../Common/Constants";
export function initializeExplorer(): Explorer {
return EmulatorExplorerFactory.createExplorer();
const explorer = new Explorer();
explorer.databaseAccount({
name: "",
id: "",
location: "",
type: "",
kind: AccountKind.DocumentDB,
tags: {
[TagNames.defaultExperience]: DefaultAccountExperience.DocumentDB
},
properties: {
documentEndpoint: "",
tableEndpoint: "",
gremlinEndpoint: "",
cassandraEndpoint: ""
}
});
explorer.isAccountReady(true);
return explorer;
}

View File

@@ -1,15 +0,0 @@
import Explorer from "../../Explorer/Explorer";
export default class HostedExplorerFactory {
public createExplorer(): Explorer {
const explorer = new Explorer();
return explorer;
}
public static reInitializeDocumentClientUtilityForExplorer(explorer: Explorer): void {
if (!!explorer) {
explorer.notificationConsoleData([]);
}
}
}

View File

@@ -1,7 +1,5 @@
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import AuthHeadersUtil from "./Authorization";
import HostedExplorerFactory from "./ExplorerFactory";
import Q from "q";
import {
AccessInputMetadata,
@@ -211,7 +209,7 @@ export default class Main {
Main._getAccessInputMetadata(Main._encryptedToken).then(
() => {
if (explorer.isConnectExplorerVisible()) {
HostedExplorerFactory.reInitializeDocumentClientUtilityForExplorer(explorer);
explorer.notificationConsoleData([]);
explorer.hideConnectExplorerForm();
}
@@ -378,8 +376,7 @@ export default class Main {
}
private static _instantiateExplorer(): Explorer {
const hostedExplorerFactory = new HostedExplorerFactory();
const explorer = hostedExplorerFactory.createExplorer();
const explorer = new Explorer();
// workaround to resolve cyclic refs with view
explorer.renewExplorerShareAccess = Main.renewExplorerAccess;
window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
@@ -483,7 +480,7 @@ export default class Main {
Main._accessInputMetadata = Main._getAccessInputMetadataFromAccountEndpoint(properties.accountEndpoint);
if (explorer.isConnectExplorerVisible()) {
HostedExplorerFactory.reInitializeDocumentClientUtilityForExplorer(explorer);
explorer.notificationConsoleData([]);
explorer.hideConnectExplorerForm();
}
@@ -570,7 +567,7 @@ export default class Main {
this._explorer.hideConnectExplorerForm();
const masterKey = Main._getMasterKey(keys);
HostedExplorerFactory.reInitializeDocumentClientUtilityForExplorer(this._explorer);
this._explorer.notificationConsoleData([]);
Main._setExplorerReady(this._explorer, masterKey, account, authorizationToken);
}

View File

@@ -1,9 +0,0 @@
import Explorer from "../../Explorer/Explorer";
export default class PortalExplorerFactory {
public createExplorer(): Explorer {
var explorer = new Explorer();
return explorer;
}
}

View File

@@ -1,10 +1,8 @@
import PortalExplorerFactory from "./ExplorerFactory";
import "../../Explorer/Tables/DataTable/DataTableBindingManager";
import Explorer from "../../Explorer/Explorer";
export function initializeExplorer(): Explorer {
const portalExplorerFactory = new PortalExplorerFactory();
const explorer = portalExplorerFactory.createExplorer();
const explorer = new Explorer();
window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
return explorer;

View File

@@ -1,10 +0,0 @@
// TODO: Should be owned by parent iframe
export enum PlatformType {
// RuntimeProxy and MongoEmulator no longer used, but kept here to preserve enum structure
RuntimeProxy,
MongoEmulator,
Hosted,
Emulator,
Portal
}

View File

@@ -1,17 +1,17 @@
import { configContext, Platform } from "../ConfigContext";
import * as ViewModels from "../Contracts/ViewModels";
import { PlatformType } from "../PlatformType";
import { PortalTokenProvider } from "./PortalTokenProvider";
export class TokenProviderFactory {
private constructor() {}
public static create(): ViewModels.TokenProvider {
const platformType = window.dataExplorerPlatform;
const platformType = configContext.platform;
switch (platformType) {
case PlatformType.Portal:
case PlatformType.Hosted:
case Platform.Portal:
case Platform.Hosted:
return new PortalTokenProvider();
case PlatformType.Emulator:
case Platform.Emulator:
default:
// should never get into this state
throw new Error(`Unknown platform ${platformType}`);

View File

@@ -1,9 +1,9 @@
import * as Constants from "../Common/Constants";
import * as AuthorizationUtils from "./AuthorizationUtils";
import { AuthType } from "../AuthType";
import { PlatformType } from "../PlatformType";
import Explorer from "../Explorer/Explorer";
import { updateUserContext } from "../UserContext";
import { Platform, updateConfigContext } from "../ConfigContext";
jest.mock("../Explorer/Explorer");
describe("AuthorizationUtils", () => {
@@ -65,12 +65,13 @@ describe("AuthorizationUtils", () => {
beforeEach(() => {
jest.clearAllMocks();
window.dataExplorer = explorer;
window.dataExplorerPlatform = PlatformType.Hosted;
updateConfigContext({
platform: Platform.Hosted
});
});
afterEach(() => {
window.dataExplorer = undefined;
window.dataExplorerPlatform = undefined;
});
it("should not open token renewal prompt if status code is undefined", () => {
@@ -89,7 +90,9 @@ describe("AuthorizationUtils", () => {
});
it("should not open token renewal prompt if running on a different platform", () => {
window.dataExplorerPlatform = PlatformType.Portal;
updateConfigContext({
platform: Platform.Portal
});
AuthorizationUtils.displayTokenRenewalPromptForStatus(Constants.HttpStatusCodes.Unauthorized);
expect(explorer.displayGuestAccessTokenRenewalPrompt).not.toHaveBeenCalled();
});

View File

@@ -3,8 +3,7 @@ import * as ViewModels from "../Contracts/ViewModels";
import AuthHeadersUtil from "../Platform/Hosted/Authorization";
import { AuthType } from "../AuthType";
import * as Logger from "../Common/Logger";
import { PlatformType } from "../PlatformType";
import { configContext } from "../ConfigContext";
import { configContext, Platform } from "../ConfigContext";
import { userContext } from "../UserContext";
export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata {
@@ -57,13 +56,12 @@ export function decryptJWTToken(token: string) {
}
export function displayTokenRenewalPromptForStatus(httpStatusCode: number): void {
const platformType = window.dataExplorerPlatform;
const explorer = window.dataExplorer;
if (
httpStatusCode == null ||
httpStatusCode != Constants.HttpStatusCodes.Unauthorized ||
platformType !== PlatformType.Hosted
configContext.platform !== Platform.Hosted
) {
return;
}

View File

@@ -17,7 +17,9 @@ const galleryItem: IGalleryItem = {
downloads: 0,
favorites: 0,
views: 0,
newCellId: undefined
newCellId: undefined,
policyViolations: undefined,
pendingScanJobIds: undefined
};
describe("GalleryUtils", () => {

View File

@@ -323,3 +323,27 @@ export function getTabTitle(tab: GalleryTab): string {
throw new Error(`Unknown tab ${tab}`);
}
}
export function filterPublishedNotebooks(
items: IGalleryItem[]
): {
published: IGalleryItem[];
underReview: IGalleryItem[];
removed: IGalleryItem[];
} {
const underReview: IGalleryItem[] = [];
const removed: IGalleryItem[] = [];
const published: IGalleryItem[] = [];
items?.forEach(item => {
if (item.policyViolations?.length > 0) {
removed.push(item);
} else if (item.pendingScanJobIds?.length > 0) {
underReview.push(item);
} else {
published.push(item);
}
});
return { published, underReview, removed };
}

View File

@@ -1,49 +1,39 @@
import { getDataExplorerWindow } from "./WindowUtils";
const createWindow = (dataExplorerPlatform: unknown, parent: Window): Window => {
// TODO: Need to `any` here since we're creating a mock window object
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockWindow: any = {};
if (dataExplorerPlatform !== undefined) {
mockWindow.dataExplorerPlatform = dataExplorerPlatform;
}
if (parent) {
mockWindow.parent = parent;
}
return mockWindow;
};
interface MockWindow {
parent?: MockWindow;
top?: MockWindow;
}
describe("WindowUtils", () => {
describe("getDataExplorerWindow", () => {
it("should return current window if current window has dataExplorerPlatform property", () => {
const currentWindow = createWindow(0, undefined);
it("should return undefined if current window is at the top", () => {
const mockWindow: MockWindow = {};
mockWindow.parent = mockWindow;
expect(getDataExplorerWindow(currentWindow)).toEqual(currentWindow);
expect(getDataExplorerWindow(mockWindow as Window)).toEqual(undefined);
});
it("should return current window's parent if current window's parent has dataExplorerPlatform property", () => {
const parentWindow = createWindow(0, undefined);
const currentWindow = createWindow(undefined, parentWindow);
it("should return current window if parent is top", () => {
const dataExplorerWindow: MockWindow = {};
const portalWindow: MockWindow = {};
dataExplorerWindow.parent = portalWindow;
dataExplorerWindow.top = portalWindow;
expect(getDataExplorerWindow(currentWindow)).toEqual(parentWindow);
expect(getDataExplorerWindow(dataExplorerWindow as Window)).toEqual(dataExplorerWindow);
});
it("should return undefined if none of the windows in the hierarchy have dataExplorerPlatform property and window's parent is reference to itself", () => {
const parentWindow = createWindow(undefined, undefined);
it("should return closest window to top if in nested windows", () => {
const terminalWindow: MockWindow = {};
const dataExplorerWindow: MockWindow = {};
const portalWindow: MockWindow = {};
dataExplorerWindow.top = portalWindow;
dataExplorerWindow.parent = portalWindow;
terminalWindow.top = portalWindow;
terminalWindow.parent = dataExplorerWindow;
// TODO: Need to `any` here since parent is a readonly property
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(parentWindow as any).parent = parentWindow; // If a window does not have a parent, its parent property is a reference to itself.
const currentWindow = createWindow(undefined, parentWindow);
expect(getDataExplorerWindow(currentWindow)).toBeUndefined();
});
it("should return undefined if none of the windows in the hierarchy have dataExplorerPlatform property and window's parent is not defined", () => {
const parentWindow = createWindow(undefined, undefined);
const currentWindow = createWindow(undefined, parentWindow);
expect(getDataExplorerWindow(currentWindow)).toBeUndefined();
expect(getDataExplorerWindow(terminalWindow as Window)).toEqual(dataExplorerWindow);
expect(getDataExplorerWindow(dataExplorerWindow as Window)).toEqual(dataExplorerWindow);
});
});
});

View File

@@ -1,23 +1,18 @@
export const getDataExplorerWindow = (currentWindow: Window): Window | undefined => {
// Start with the current window and traverse up the parent hierarchy to find a window
// with `dataExplorerPlatform` property
let dataExplorerWindow: Window | undefined = currentWindow;
// Data explorer is always loaded in an iframe, so traverse the parents until we hit the top and return the first child window.
try {
// TODO: Need to `any` here since the window imports Explorer which can't be in strict mode yet
// eslint-disable-next-line @typescript-eslint/no-explicit-any
while (dataExplorerWindow && (dataExplorerWindow as any).dataExplorerPlatform === undefined) {
// If a window does not have a parent, its parent property is a reference to itself.
if (dataExplorerWindow.parent === dataExplorerWindow) {
dataExplorerWindow = undefined;
} else {
dataExplorerWindow = dataExplorerWindow.parent;
while (currentWindow) {
if (currentWindow.parent === currentWindow) {
return undefined;
}
if (currentWindow.parent === currentWindow.top) {
return currentWindow;
}
currentWindow = currentWindow.parent;
}
} catch (error) {
// This can happen if we come across parent from a different origin
dataExplorerWindow = undefined;
// Hitting a cross domain error means we are in the portal and the current window is data explorer
return currentWindow;
}
return dataExplorerWindow;
return undefined;
};

2
src/global.d.ts vendored
View File

@@ -1,11 +1,9 @@
import { AuthType } from "./AuthType";
import { PlatformType } from "./PlatformType";
import Explorer from "./Explorer/Explorer";
declare global {
interface Window {
authType: AuthType;
dataExplorerPlatform: PlatformType;
dataExplorer: Explorer;
__REACT_DEVTOOLS_GLOBAL_HOOK__: any;
$: any;

View File

@@ -56,7 +56,6 @@
"./src/GitHub/GitHubConnector.ts",
"./src/Index.ts",
"./src/NotebookWorkspaceManager/NotebookWorkspaceResourceProviderMockClients.ts",
"./src/PlatformType.ts",
"./src/ReactDevTools.ts",
"./src/ResourceProvider/IResourceProviderClient.ts",
"./src/Shared/ExplorerSettings.ts",