Refactor NotificationsClient (#270)

This commit is contained in:
Steve Faulkner 2020-10-12 22:10:28 -05:00 committed by GitHub
parent 3b64d75322
commit cfb9a0b321
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 75 additions and 157 deletions

View File

@ -1,46 +0,0 @@
import "jquery";
import * as Q from "q";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { userContext } from "../UserContext";
export class NotificationsClientBase {
private _extensionEndpoint: string;
private _notificationsApiSuffix: string;
protected constructor(notificationsApiSuffix: string) {
this._notificationsApiSuffix = notificationsApiSuffix;
}
public fetchNotifications(): Q.Promise<DataModels.Notification[]> {
const deferred: Q.Deferred<DataModels.Notification[]> = Q.defer<DataModels.Notification[]>();
const databaseAccount = userContext.databaseAccount;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const url = `${this._extensionEndpoint}${this._notificationsApiSuffix}?accountName=${databaseAccount.name}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers: any = {};
headers[authorizationHeader.header] = authorizationHeader.token;
$.ajax({
url: url,
type: "GET",
headers: headers,
cache: false
}).then(
(notifications: DataModels.Notification[], textStatus: string, xhr: JQueryXHR<any>) => {
deferred.resolve(notifications);
},
(xhr: JQueryXHR<any>, textStatus: string, error: any) => {
deferred.reject(xhr.responseText);
}
);
return deferred.promise;
}
public setExtensionEndpoint(extensionEndpoint: string): void {
this._extensionEndpoint = extensionEndpoint;
}
}

View File

@ -0,0 +1,41 @@
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { userContext } from "../UserContext";
import { configContext, Platform } from "../ConfigContext";
const notificationsPath = () => {
switch (configContext.platform) {
case Platform.Hosted:
return "/api/guest/notifications";
case Platform.Portal:
return "/api/notifications";
default:
throw new Error(`Unknown platform: ${configContext.platform}`);
}
};
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
if (configContext.platform === Platform.Emulator) {
return [];
}
const databaseAccount = userContext.databaseAccount;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
databaseAccount.name
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers = { [authorizationHeader.header]: authorizationHeader.token };
const response = await window.fetch(url, {
headers
});
if (!response.ok) {
throw new Error(await response.text());
}
return (await response.json()) as DataModels.Notification[];
};

View File

@ -103,9 +103,7 @@ describe("SettingsComponent", () => {
let settingsComponentInstance = new SettingsComponent(baseProps);
expect(settingsComponentInstance.shouldShowKeyspaceSharedThroughputMessage()).toEqual(false);
const newContainer = new Explorer({
notificationsClient: undefined
});
const newContainer = new Explorer();
newContainer.isPreferredApiCassandra = ko.computed(() => true);
const newCollection = { ...collection };
@ -146,9 +144,7 @@ describe("SettingsComponent", () => {
let settingsComponentInstance = new SettingsComponent(baseProps);
expect(settingsComponentInstance.hasConflictResolution()).toEqual(undefined);
const newContainer = new Explorer({
notificationsClient: undefined
});
const newContainer = new Explorer();
newContainer.databaseAccount = ko.observable({
id: undefined,
name: undefined,

View File

@ -12,9 +12,7 @@ import * as SharedConstants from "../../../../Shared/Constants";
import ko from "knockout";
describe("ScaleComponent", () => {
const nonNationalCloudContainer = new Explorer({
notificationsClient: undefined
});
const nonNationalCloudContainer = new Explorer();
nonNationalCloudContainer.getPlatformType = () => PlatformType.Portal;
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
@ -87,9 +85,7 @@ describe("ScaleComponent", () => {
});
it("autoScale enabled", () => {
const newContainer = new Explorer({
notificationsClient: undefined
});
const newContainer = new Explorer();
newContainer.databaseAccount({
id: undefined,

View File

@ -105,9 +105,7 @@ describe("SubSettingsComponent", () => {
});
it("partitionKey not visible", () => {
const newContainer = new Explorer({
notificationsClient: undefined
});
const newContainer = new Explorer();
newContainer.isPreferredApiCassandra = ko.computed(() => true);
const props = { ...baseProps, container: newContainer };

View File

@ -3,9 +3,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
import Explorer from "../../Explorer";
import ko from "knockout";
export const container = new Explorer({
notificationsClient: undefined
});
export const container = new Explorer();
export const collection = ({
container: container,

View File

@ -1051,7 +1051,6 @@ exports[`SettingsComponent renders 1`] = `
"parameters": [Function],
},
"notificationConsoleData": [Function],
"notificationsClient": undefined,
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
@ -2358,7 +2357,6 @@ exports[`SettingsComponent renders 1`] = `
"parameters": [Function],
},
"notificationConsoleData": [Function],
"notificationsClient": undefined,
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
@ -3678,7 +3676,6 @@ exports[`SettingsComponent renders 1`] = `
"parameters": [Function],
},
"notificationConsoleData": [Function],
"notificationsClient": undefined,
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],
@ -4985,7 +4982,6 @@ exports[`SettingsComponent renders 1`] = `
"parameters": [Function],
},
"notificationConsoleData": [Function],
"notificationsClient": undefined,
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function],

View File

@ -82,7 +82,6 @@ import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger";
import { NotificationsClientBase } from "../Common/NotificationsClientBase";
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
import TabsBase from "./Tabs/TabsBase";
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
@ -99,9 +98,6 @@ enum ShareAccessToggleState {
Read
}
interface ExplorerOptions {
notificationsClient: NotificationsClientBase;
}
interface AdHocAccessData {
readWriteUrl: string;
readUrl: string;
@ -141,7 +137,6 @@ export default class Explorer {
public serverId: ko.Observable<string>;
public armEndpoint: ko.Observable<string>;
public isTryCosmosDBSubscription: ko.Observable<boolean>;
public notificationsClient: NotificationsClientBase;
public queriesClient: QueriesClient;
public tableDataClient: TableDataClient;
public splitter: Splitter;
@ -270,7 +265,7 @@ export default class Explorer {
private static readonly MaxNbDatabasesToAutoExpand = 5;
constructor(options: ExplorerOptions) {
constructor() {
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
dataExplorerArea: Constants.Areas.ResourceTree
});
@ -376,7 +371,6 @@ export default class Explorer {
}
});
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
this.notificationsClient = options.notificationsClient;
this.features = ko.observable();
this.serverId = ko.observable<string>();
@ -1910,7 +1904,6 @@ export default class Explorer {
this.features(inputs.features);
this.serverId(inputs.serverId);
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
this.notificationsClient.setExtensionEndpoint(configContext.BACKEND_ENDPOINT);
this.databaseAccount(databaseAccount);
this.subscriptionType(inputs.subscriptionType);
this.quotaId(inputs.quotaId);

View File

@ -40,7 +40,7 @@ describe("Add Collection Pane", () => {
};
beforeEach(() => {
explorer = new Explorer({ notificationsClient: null });
explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
});

View File

@ -40,9 +40,7 @@ describe("Add Database Pane", () => {
};
beforeEach(() => {
explorer = new Explorer({
notificationsClient: null
});
explorer = new Explorer();
});
it("should be true if subscription type is Benefits", () => {

View File

@ -17,7 +17,7 @@ describe("Delete Collection Confirmation Pane", () => {
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ notificationsClient: null });
explorer = new Explorer();
});
it("should be true if 1 database and 1 collection", () => {
@ -56,7 +56,7 @@ describe("Delete Collection Confirmation Pane", () => {
describe("shouldRecordFeedback()", () => {
it("should return true if last collection and database does not have shared throughput else false", () => {
let fakeExplorer = new Explorer({ notificationsClient: null });
let fakeExplorer = new Explorer();
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
fakeExplorer.refreshAllDatabases = () => Q.resolve();

View File

@ -22,7 +22,7 @@ describe("Delete Database Confirmation Pane", () => {
});
beforeEach(() => {
explorer = new Explorer({ notificationsClient: null });
explorer = new Explorer();
});
it("should be true if only 1 database", () => {

View File

@ -7,7 +7,7 @@ describe("Settings Pane", () => {
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ notificationsClient: null });
explorer = new Explorer();
});
it("should be true for SQL API", () => {

View File

@ -6,7 +6,7 @@ import Explorer from "../Explorer";
jest.mock("../Explorer");
const createExplorer = () => {
const mock = new Explorer({} as any);
const mock = new Explorer();
mock.selectedNode = ko.observable();
mock.isNotebookEnabled = ko.observable(false);
mock.addCollectionText = ko.observable("add collection");

View File

@ -27,13 +27,9 @@ describe("Documents tab", () => {
});
describe("showPartitionKey", () => {
const explorer = new Explorer({
notificationsClient: null
});
const explorer = new Explorer();
const mongoExplorer = new Explorer({
notificationsClient: null
});
const mongoExplorer = new Explorer();
mongoExplorer.defaultExperience(Constants.DefaultAccountExperience.MongoDB);
const collectionWithoutPartitionKey = <ViewModels.Collection>(<unknown>{

View File

@ -49,7 +49,7 @@ describe("Query Tab", () => {
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ notificationsClient: null });
explorer = new Explorer();
});
it("should be true for accounts using SQL API", () => {
@ -69,7 +69,7 @@ describe("Query Tab", () => {
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ notificationsClient: null });
explorer = new Explorer();
});
it("should be visible when using a supported API", () => {

View File

@ -79,7 +79,7 @@ describe("Settings tab", () => {
};
beforeEach(() => {
explorer = new Explorer({ notificationsClient: null });
explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
});
@ -178,7 +178,7 @@ describe("Settings tab", () => {
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ notificationsClient: null });
explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
});
@ -256,7 +256,7 @@ describe("Settings tab", () => {
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ notificationsClient: null });
explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
});
@ -337,9 +337,7 @@ describe("Settings tab", () => {
}
function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) {
const explorer = new Explorer({
notificationsClient: null
});
const explorer = new Explorer();
explorer.defaultExperience(defaultApi);
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
@ -469,9 +467,7 @@ describe("Settings tab", () => {
describe("AutoPilot", () => {
function getCollection(autoPilotTier: DataModels.AutopilotTier) {
const explorer = new Explorer({
notificationsClient: null
});
const explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
explorer.databaseAccount({

View File

@ -16,7 +16,7 @@ describe("Tabs manager tests", () => {
let documentsTab: DocumentsTab;
beforeAll(() => {
explorer = new Explorer({ notificationsClient: undefined });
explorer = new Explorer();
explorer.databaseAccount = ko.observable<DataModels.DatabaseAccount>({
id: "test",
name: "test",

View File

@ -40,6 +40,7 @@ import { configContext } from "../../ConfigContext";
import Explorer from "../Explorer";
import { userContext } from "../../UserContext";
import TabsBase from "../Tabs/TabsBase";
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
export default class Collection implements ViewModels.Collection {
public nodeKind: string;
@ -1213,7 +1214,7 @@ export default class Collection implements ViewModels.Collection {
}
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
this.container.notificationsClient.fetchNotifications().then(
fetchPortalNotifications().then(
(notifications: DataModels.Notification[]) => {
if (!notifications || notifications.length === 0) {
deferred.resolve(undefined);

View File

@ -15,6 +15,7 @@ import Explorer from "../Explorer";
import { readCollections } from "../../Common/dataAccess/readCollections";
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
export default class Database implements ViewModels.Database {
public nodeKind: string;
@ -220,8 +221,8 @@ export default class Database implements ViewModels.Database {
}
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
this.container.notificationsClient.fetchNotifications().then(
(notifications: DataModels.Notification[]) => {
fetchPortalNotifications().then(
notifications => {
if (!notifications || notifications.length === 0) {
deferred.resolve(undefined);
return;

View File

@ -2,13 +2,9 @@ import { AccountKind, TagNames, DefaultAccountExperience } from "../../Common/Co
import Explorer from "../../Explorer/Explorer";
import { NotificationsClient } from "./NotificationsClient";
export default class EmulatorExplorerFactory {
public static createExplorer(): Explorer {
const explorer: Explorer = new Explorer({
notificationsClient: new NotificationsClient()
});
const explorer: Explorer = new Explorer();
explorer.databaseAccount({
name: "",
id: "",

View File

@ -1,16 +0,0 @@
import Q from "q";
import * as DataModels from "../../Contracts/DataModels";
import { NotificationsClientBase } from "../../Common/NotificationsClientBase";
export class NotificationsClient extends NotificationsClientBase {
private static readonly _notificationsApiSuffix: string = "/api/notifications";
public constructor() {
super(NotificationsClient._notificationsApiSuffix);
}
public fetchNotifications(): Q.Promise<DataModels.Notification[]> {
// no notifications for the emulator
return Q([]);
}
}

View File

@ -1,11 +1,8 @@
import Explorer from "../../Explorer/Explorer";
import { NotificationsClient } from "./NotificationsClient";
export default class HostedExplorerFactory {
public createExplorer(): Explorer {
const explorer = new Explorer({
notificationsClient: new NotificationsClient()
});
const explorer = new Explorer();
return explorer;
}

View File

@ -1,9 +0,0 @@
import { NotificationsClientBase } from "../../Common/NotificationsClientBase";
export class NotificationsClient extends NotificationsClientBase {
private static readonly _notificationsApiSuffix: string = "/api/guest/notifications";
public constructor() {
super(NotificationsClient._notificationsApiSuffix);
}
}

View File

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

View File

@ -1,9 +0,0 @@
import { NotificationsClientBase } from "../../Common/NotificationsClientBase";
export class NotificationsClient extends NotificationsClientBase {
private static readonly _notificationsApiSuffix: string = "/api/notifications";
public constructor() {
super(NotificationsClient._notificationsApiSuffix);
}
}

View File

@ -9,9 +9,7 @@ describe("TabRouteHandler", () => {
let tabRouteHandler: TabRouteHandler;
beforeAll(() => {
(<any>window).dataExplorer = new Explorer({
notificationsClient: null
}); // create a mock to avoid null refs
(<any>window).dataExplorer = new Explorer(); // create a mock to avoid null refs
});
beforeEach(() => {

View File

@ -60,7 +60,7 @@ describe("AuthorizationUtils", () => {
});
describe("displayTokenRenewalPromptForStatus()", () => {
let explorer = new Explorer({} as any) as jest.Mocked<Explorer>;
let explorer = new Explorer() as jest.Mocked<Explorer>;
beforeEach(() => {
jest.clearAllMocks();