Add a feature flag to override Juno endpoint (#700)
* Add a feature flag to override Juno endpoint * Fix build
This commit is contained in:
parent
f2585bba14
commit
914e969083
|
@ -26,6 +26,7 @@ export interface ConfigContext {
|
||||||
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
|
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
|
||||||
hostedExplorerURL: string;
|
hostedExplorerURL: string;
|
||||||
armAPIVersion?: string;
|
armAPIVersion?: string;
|
||||||
|
allowedJunoOrigins: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default configuration
|
// Default configuration
|
||||||
|
@ -53,6 +54,13 @@ let configContext: Readonly<ConfigContext> = {
|
||||||
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
|
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
|
||||||
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
|
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
|
||||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||||
|
allowedJunoOrigins: [
|
||||||
|
"https://juno-test.documents-dev.windows-int.net",
|
||||||
|
"https://juno-test2.documents-dev.windows-int.net",
|
||||||
|
"https://tools.cosmos.azure.com",
|
||||||
|
"https://tools-staging.cosmos.azure.com",
|
||||||
|
"https://localhost",
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resetConfigContext(): void {
|
export function resetConfigContext(): void {
|
||||||
|
@ -86,13 +94,18 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
||||||
});
|
});
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
try {
|
try {
|
||||||
const { allowedParentFrameOrigins, ...externalConfig } = await response.json();
|
const { allowedParentFrameOrigins, allowedJunoOrigins, ...externalConfig } = await response.json();
|
||||||
Object.assign(configContext, externalConfig);
|
Object.assign(configContext, externalConfig);
|
||||||
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
|
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins],
|
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (allowedJunoOrigins && allowedJunoOrigins.length > 0) {
|
||||||
|
updateConfigContext({
|
||||||
|
allowedJunoOrigins: [...configContext.allowedJunoOrigins, ...allowedJunoOrigins],
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unable to parse json in config file");
|
console.error("Unable to parse json in config file");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
import { HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
||||||
import { IPinnedRepo, JunoClient, IPublishNotebookRequest } from "./JunoClient";
|
|
||||||
import { configContext } from "../ConfigContext";
|
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
|
||||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||||
import { updateUserContext, userContext } from "../UserContext";
|
import { updateUserContext, userContext } from "../UserContext";
|
||||||
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
|
import { IPinnedRepo, IPublishNotebookRequest, JunoClient } from "./JunoClient";
|
||||||
|
|
||||||
const sampleSubscriptionId = "subscriptionId";
|
const sampleSubscriptionId = "subscriptionId";
|
||||||
|
|
||||||
|
@ -157,7 +156,7 @@ describe("Gallery", () => {
|
||||||
const response = await junoClient.getSampleNotebooks();
|
const response = await junoClient.getSampleNotebooks();
|
||||||
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/samples`, undefined);
|
expect(window.fetch).toBeCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/samples`, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getPublicNotebooks", async () => {
|
it("getPublicNotebooks", async () => {
|
||||||
|
@ -169,7 +168,7 @@ describe("Gallery", () => {
|
||||||
const response = await junoClient.getPublicNotebooks();
|
const response = await junoClient.getPublicNotebooks();
|
||||||
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/public`, undefined);
|
expect(window.fetch).toBeCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/public`, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getNotebook", async () => {
|
it("getNotebook", async () => {
|
||||||
|
@ -182,7 +181,7 @@ describe("Gallery", () => {
|
||||||
const response = await junoClient.getNotebookInfo(id);
|
const response = await junoClient.getNotebookInfo(id);
|
||||||
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/${id}`);
|
expect(window.fetch).toBeCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/${id}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getNotebookContent", async () => {
|
it("getNotebookContent", async () => {
|
||||||
|
@ -195,7 +194,7 @@ describe("Gallery", () => {
|
||||||
const response = await junoClient.getNotebookContent(id);
|
const response = await junoClient.getNotebookContent(id);
|
||||||
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/${id}/content`);
|
expect(window.fetch).toBeCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/${id}/content`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("increaseNotebookViews", async () => {
|
it("increaseNotebookViews", async () => {
|
||||||
|
@ -208,7 +207,7 @@ describe("Gallery", () => {
|
||||||
const response = await junoClient.increaseNotebookViews(id);
|
const response = await junoClient.increaseNotebookViews(id);
|
||||||
|
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(`${configContext.JUNO_ENDPOINT}/api/notebooks/gallery/${id}/views`, {
|
expect(window.fetch).toBeCalledWith(`${JunoClient.getJunoEndpoint()}/api/notebooks/gallery/${id}/views`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -225,7 +224,9 @@ describe("Gallery", () => {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(
|
expect(window.fetch).toBeCalledWith(
|
||||||
`${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/${id}/downloads`,
|
`${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${
|
||||||
|
sampleDatabaseAccount.name
|
||||||
|
}/gallery/${id}/downloads`,
|
||||||
{
|
{
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -248,7 +249,9 @@ describe("Gallery", () => {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(
|
expect(window.fetch).toBeCalledWith(
|
||||||
`${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/${id}/favorite`,
|
`${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${
|
||||||
|
sampleDatabaseAccount.name
|
||||||
|
}/gallery/${id}/favorite`,
|
||||||
{
|
{
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -271,7 +274,9 @@ describe("Gallery", () => {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(
|
expect(window.fetch).toBeCalledWith(
|
||||||
`${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/${id}/unfavorite`,
|
`${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${
|
||||||
|
sampleDatabaseAccount.name
|
||||||
|
}/gallery/${id}/unfavorite`,
|
||||||
{
|
{
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -293,7 +298,9 @@ describe("Gallery", () => {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(
|
expect(window.fetch).toBeCalledWith(
|
||||||
`${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/favorites`,
|
`${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${
|
||||||
|
sampleDatabaseAccount.name
|
||||||
|
}/gallery/favorites`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
|
@ -314,7 +321,9 @@ describe("Gallery", () => {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(
|
expect(window.fetch).toBeCalledWith(
|
||||||
`${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/published`,
|
`${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${
|
||||||
|
sampleDatabaseAccount.name
|
||||||
|
}/gallery/published`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
[authorizationHeader.header]: authorizationHeader.token,
|
||||||
|
@ -336,7 +345,9 @@ describe("Gallery", () => {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(
|
expect(window.fetch).toBeCalledWith(
|
||||||
`${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery/${id}`,
|
`${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${
|
||||||
|
sampleDatabaseAccount.name
|
||||||
|
}/gallery/${id}`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -365,7 +376,9 @@ describe("Gallery", () => {
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
expect(response.status).toBe(HttpStatusCodes.OK);
|
expect(response.status).toBe(HttpStatusCodes.OK);
|
||||||
expect(window.fetch).toBeCalledWith(
|
expect(window.fetch).toBeCalledWith(
|
||||||
`${configContext.JUNO_ENDPOINT}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${sampleDatabaseAccount.name}/gallery`,
|
`${JunoClient.getJunoEndpoint()}/api/notebooks/subscriptions/${sampleSubscriptionId}/databaseAccounts/${
|
||||||
|
sampleDatabaseAccount.name
|
||||||
|
}/gallery`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { IGitHubResponse } from "../GitHub/GitHubClient";
|
||||||
import { IGitHubOAuthToken } from "../GitHub/GitHubOAuthService";
|
import { IGitHubOAuthToken } from "../GitHub/GitHubOAuthService";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
import { number } from "prop-types";
|
|
||||||
|
|
||||||
export interface IJunoResponse<T> {
|
export interface IJunoResponse<T> {
|
||||||
status: number;
|
status: number;
|
||||||
|
@ -484,8 +483,20 @@ export class JunoClient {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// public for tests
|
||||||
|
public static getJunoEndpoint(): string {
|
||||||
|
const junoEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
||||||
|
if (configContext.allowedJunoOrigins.indexOf(new URL(junoEndpoint).origin) === -1) {
|
||||||
|
const error = `${junoEndpoint} not allowed as juno endpoint`;
|
||||||
|
console.error(error);
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return junoEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
private getNotebooksUrl(): string {
|
private getNotebooksUrl(): string {
|
||||||
return `${configContext.JUNO_ENDPOINT}/api/notebooks`;
|
return `${JunoClient.getJunoEndpoint()}/api/notebooks`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAccount(): string {
|
private getAccount(): string {
|
||||||
|
@ -501,7 +512,7 @@ export class JunoClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAnalyticsUrl(): string {
|
private getAnalyticsUrl(): string {
|
||||||
return `${configContext.JUNO_ENDPOINT}/api/analytics`;
|
return `${JunoClient.getJunoEndpoint()}/api/analytics`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getHeaders(): HeadersInit {
|
private static getHeaders(): HeadersInit {
|
||||||
|
|
|
@ -13,6 +13,7 @@ export type Features = {
|
||||||
readonly enableTtl: boolean;
|
readonly enableTtl: boolean;
|
||||||
readonly executeSproc: boolean;
|
readonly executeSproc: boolean;
|
||||||
readonly hostedDataExplorer: boolean;
|
readonly hostedDataExplorer: boolean;
|
||||||
|
readonly junoEndpoint?: string;
|
||||||
readonly livyEndpoint?: string;
|
readonly livyEndpoint?: string;
|
||||||
readonly notebookBasePath?: string;
|
readonly notebookBasePath?: string;
|
||||||
readonly notebookServerToken?: string;
|
readonly notebookServerToken?: string;
|
||||||
|
@ -27,7 +28,8 @@ export type Features = {
|
||||||
export function extractFeatures(given = new URLSearchParams(window.location.search)): Features {
|
export function extractFeatures(given = new URLSearchParams(window.location.search)): Features {
|
||||||
const downcased = new URLSearchParams();
|
const downcased = new URLSearchParams();
|
||||||
const set = (value: string, key: string) => downcased.set(key.toLowerCase(), value);
|
const set = (value: string, key: string) => downcased.set(key.toLowerCase(), value);
|
||||||
const get = (key: string, defaultValue?: string) => downcased.get("feature." + key) ?? defaultValue;
|
const get = (key: string, defaultValue?: string) =>
|
||||||
|
downcased.get("feature." + key) ?? downcased.get(key) ?? defaultValue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new URLSearchParams(window.parent.location.search).forEach(set);
|
new URLSearchParams(window.parent.location.search).forEach(set);
|
||||||
|
@ -52,6 +54,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
||||||
enableTtl: "true" === get("enablettl"),
|
enableTtl: "true" === get("enablettl"),
|
||||||
executeSproc: "true" === get("dataexplorerexecutesproc"),
|
executeSproc: "true" === get("dataexplorerexecutesproc"),
|
||||||
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
|
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
|
||||||
|
junoEndpoint: get("junoendpoint"),
|
||||||
livyEndpoint: get("livyendpoint"),
|
livyEndpoint: get("livyendpoint"),
|
||||||
notebookBasePath: get("notebookbasepath"),
|
notebookBasePath: get("notebookbasepath"),
|
||||||
notebookServerToken: get("notebookservertoken"),
|
notebookServerToken: get("notebookservertoken"),
|
||||||
|
|
Loading…
Reference in New Issue