diff --git a/less/documentDB.less b/less/documentDB.less index 79e9435c3..7ccaf4255 100644 --- a/less/documentDB.less +++ b/less/documentDB.less @@ -2077,7 +2077,7 @@ a:link { .resourceTreeAndTabs { display: flex; flex: 1 1 auto; - overflow-x: auto; + overflow-x: clip; overflow-y: auto; height: 100%; } @@ -2245,7 +2245,7 @@ a:link { } .refreshColHeader { - padding: 3px 6px 6px 6px; + padding: 3px 6px 10px 0px !important; } .refreshColHeader:hover { @@ -2869,31 +2869,31 @@ a:link { } } -settings-pane { - .settingsSection { - border-bottom: 1px solid @BaseMedium; - margin-right: 24px; - padding: @MediumSpace 0px; +.settingsSection { + border-bottom: 1px solid @BaseMedium; + margin-right: 24px; + padding: @MediumSpace 0px; - &:first-child { - padding-top: 0px; - } + &:first-child { + padding-top: 0px; + padding-bottom: 10px; + } - &:last-child { - border-bottom: none; - } + &:last-child { + border-bottom: none; + } - .settingsSectionPart { - padding-left: 8px; - } + .settingsSectionPart { + padding-left: 8px; + } - .settingsSectionLabel { - margin-bottom: @DefaultSpace; - } + .settingsSectionLabel { + margin-bottom: @DefaultSpace; + margin-right: 5px; + } - .pageOptionsPart { - padding-bottom: @MediumSpace; - } + .pageOptionsPart { + padding-bottom: @MediumSpace; } } diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index c627c65fd..36dfa83a1 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -412,3 +412,11 @@ export class TerminalQueryParams { public static readonly SubscriptionId = "subscriptionId"; public static readonly TerminalEndpoint = "terminalEndpoint"; } + +export class JunoEndpoints { + public static readonly Test = "https://juno-test.documents-dev.windows-int.net"; + public static readonly Test2 = "https://juno-test2.documents-dev.windows-int.net"; + public static readonly Test3 = "https://juno-test3.documents-dev.windows-int.net"; + public static readonly Prod = "https://tools.cosmos.azure.com"; + public static readonly Stage = "https://tools-staging.cosmos.azure.com"; +} diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index 89217fbef..d7a8dd7c0 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -1,3 +1,5 @@ +import { JunoEndpoints } from "Common/Constants"; + export enum Platform { Portal = "Portal", Hosted = "Hosted", @@ -23,6 +25,7 @@ export interface ConfigContext { PROXY_PATH?: string; JUNO_ENDPOINT: string; GITHUB_CLIENT_ID: string; + GITHUB_TEST_ENV_CLIENT_ID: string; GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it. IS_MPAC: boolean; hostedExplorerURL: string; @@ -53,16 +56,17 @@ let configContext: Readonly = { GRAPH_API_VERSION: "1.6", ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net", ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net", - GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306 + GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306 + GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772 IS_MPAC: false, JUNO_ENDPOINT: "https://tools.cosmos.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://juno-test3.documents-dev.windows-int.net", - "https://tools.cosmos.azure.com", - "https://tools-staging.cosmos.azure.com", + JunoEndpoints.Test, + JunoEndpoints.Test2, + JunoEndpoints.Test3, + JunoEndpoints.Prod, + JunoEndpoints.Stage, "https://localhost", ], }; @@ -148,3 +152,4 @@ export async function initializeConfiguration(): Promise { } export { configContext }; + diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 1036dce6d..88f32e2e8 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -7,7 +7,7 @@ import shallow from "zustand/shallow"; import { AuthType } from "../AuthType"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import * as Constants from "../Common/Constants"; -import { ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants"; +import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants"; import { readCollection } from "../Common/dataAccess/readCollection"; import { readDatabases } from "../Common/dataAccess/readDatabases"; import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils"; @@ -16,7 +16,6 @@ import { QueriesClient } from "../Common/QueriesClient"; import * as DataModels from "../Contracts/DataModels"; import { ContainerConnectionInfo, - IContainerData, IPhoenixConnectionInfoResult, IProvisionData, IResponse, @@ -377,18 +376,36 @@ export default class Explorer { }; useNotebook.getState().setConnectionInfo(connectionStatus); try { + TelemetryProcessor.traceStart(Action.PhoenixConnection, { + dataExplorerArea: Areas.Notebook, + }); useNotebook.getState().setIsAllocating(true); const connectionInfo = await this.phoenixClient.allocateContainer(provisionData); + if (connectionInfo.status !== HttpStatusCodes.OK) { + throw new Error(`Received status code: ${connectionInfo?.status}`); + } + if (!connectionInfo?.data?.notebookServerUrl) { + throw new Error(`NotebookServerUrl is invalid!`); + } await this.setNotebookInfo(connectionInfo, connectionStatus); + TelemetryProcessor.traceSuccess(Action.PhoenixConnection, { + dataExplorerArea: Areas.Notebook, + }); } catch (error) { + TelemetryProcessor.traceFailure(Action.PhoenixConnection, { + dataExplorerArea: Areas.Notebook, + error: getErrorMessage(error), + errorStack: getErrorStack(error), + }); connectionStatus.status = ConnectionStatusType.Failed; useNotebook.getState().resetContainerConnection(connectionStatus); throw error; + } finally { + useNotebook.getState().setIsAllocating(false); + this.refreshCommandBarButtons(); + this.refreshNotebookList(); + this._isInitializingNotebooks = false; } - this.refreshCommandBarButtons(); - this.refreshNotebookList(); - - this._isInitializingNotebooks = false; } } @@ -396,27 +413,22 @@ export default class Explorer { connectionInfo: IResponse, connectionStatus: DataModels.ContainerConnectionInfo ) { - if (connectionInfo.status === HttpStatusCodes.OK && connectionInfo.data && connectionInfo.data.notebookServerUrl) { - const containerData: IContainerData = { - forwardingId: connectionInfo.data.forwardingId, - }; - await this.phoenixClient.initiateContainerHeartBeat(containerData); + const containerData = { + forwardingId: connectionInfo.data.forwardingId, + dbAccountName: userContext.databaseAccount.name, + }; + await this.phoenixClient.initiateContainerHeartBeat(containerData); - connectionStatus.status = ConnectionStatusType.Connected; - useNotebook.getState().setConnectionInfo(connectionStatus); - useNotebook.getState().setNotebookServerInfo({ - notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl, - authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken, - forwardingId: connectionInfo.data.forwardingId, - }); - this.notebookManager?.notebookClient - .getMemoryUsage() - .then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo)); - } else { - connectionStatus.status = ConnectionStatusType.Failed; - useNotebook.getState().resetContainerConnection(connectionStatus); - } - useNotebook.getState().setIsAllocating(false); + connectionStatus.status = ConnectionStatusType.Connected; + useNotebook.getState().setConnectionInfo(connectionStatus); + useNotebook.getState().setNotebookServerInfo({ + notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl, + authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken, + forwardingId: connectionInfo.data.forwardingId, + }); + this.notebookManager?.notebookClient + .getMemoryUsage() + .then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo)); } public resetNotebookWorkspace(): void { @@ -501,7 +513,9 @@ export default class Explorer { logConsoleError(error); return; } - + TelemetryProcessor.traceStart(Action.PhoenixResetWorkspace, { + dataExplorerArea: Areas.Notebook, + }); if (useNotebook.getState().isPhoenix) { useTabs.getState().closeAllNotebookTabs(true); connectionStatus = { @@ -510,27 +524,24 @@ export default class Explorer { useNotebook.getState().setConnectionInfo(connectionStatus); } const connectionInfo = await this.notebookManager?.notebookClient.resetWorkspace(); - if (connectionInfo && connectionInfo.status && connectionInfo.status === HttpStatusCodes.OK) { - if (useNotebook.getState().isPhoenix && connectionInfo.data && connectionInfo.data.notebookServerUrl) { - await this.setNotebookInfo(connectionInfo, connectionStatus); - useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - } - logConsoleInfo("Successfully reset notebook workspace"); - TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace); - } else { - logConsoleError(`Failed to reset notebook workspace`); - TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace); - if (useNotebook.getState().isPhoenix) { - connectionStatus = { - status: ConnectionStatusType.Reconnect, - }; - useNotebook.getState().resetContainerConnection(connectionStatus); - useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - } + if (connectionInfo?.status !== HttpStatusCodes.OK) { + throw new Error(`Reset Workspace: Received status code- ${connectionInfo?.status}`); } + if (!connectionInfo?.data?.notebookServerUrl) { + throw new Error(`Reset Workspace: NotebookServerUrl is invalid!`); + } + if (useNotebook.getState().isPhoenix) { + await this.setNotebookInfo(connectionInfo, connectionStatus); + useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); + } + logConsoleInfo("Successfully reset notebook workspace"); + TelemetryProcessor.traceSuccess(Action.PhoenixResetWorkspace, { + dataExplorerArea: Areas.Notebook, + }); } catch (error) { logConsoleError(`Failed to reset notebook workspace: ${error}`); - TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, { + TelemetryProcessor.traceFailure(Action.PhoenixResetWorkspace, { + dataExplorerArea: Areas.Notebook, error: getErrorMessage(error), errorStack: getErrorStack(error), }); @@ -538,7 +549,7 @@ export default class Explorer { connectionStatus = { status: ConnectionStatusType.Failed, }; - useNotebook.getState().setConnectionInfo(connectionStatus); + useNotebook.getState().resetContainerConnection(connectionStatus); useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); } throw error; diff --git a/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx index 0c3568ecf..4b0535175 100644 --- a/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx +++ b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx @@ -23,10 +23,12 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor export interface AddDatabasePaneProps { explorer: Explorer; + buttonElement?: HTMLElement; } export const AddDatabasePanel: FunctionComponent = ({ explorer: container, + buttonElement, }: AddDatabasePaneProps) => { const closeSidePanel = useSidePanel((state) => state.closeSidePanel); let throughput: number; @@ -78,6 +80,9 @@ export const AddDatabasePanel: FunctionComponent = ({ dataExplorerArea: Constants.Areas.ContextualPane, }; TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage); + if (buttonElement) { + buttonElement.focus(); + } }, []); const onSubmit = () => { diff --git a/src/Explorer/Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane.tsx b/src/Explorer/Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane.tsx index 064aadd5a..81653a025 100644 --- a/src/Explorer/Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane.tsx +++ b/src/Explorer/Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane.tsx @@ -1,6 +1,6 @@ import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/react"; import { useBoolean } from "@fluentui/react-hooks"; -import React, { FunctionComponent, useState } from "react"; +import React, { FunctionComponent, useRef, useState } from "react"; import AddPropertyIcon from "../../../../images/Add-property.svg"; import { useSidePanel } from "../../../hooks/useSidePanel"; import { logConsoleError } from "../../../Utils/NotificationConsoleUtils"; @@ -25,19 +25,16 @@ interface UnwrappedExecuteSprocParam { export const ExecuteSprocParamsPane: FunctionComponent = ({ storedProcedure, }: ExecuteSprocParamsPaneProps): JSX.Element => { + const paramKeyValuesRef = useRef([{ key: "string", text: "" }]); + const partitionValueRef = useRef(); + const partitionKeyRef = useRef("string"); const closeSidePanel = useSidePanel((state) => state.closeSidePanel); + const [numberOfParams, setNumberOfParams] = useState(1); const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false); - const [paramKeyValues, setParamKeyValues] = useState([{ key: "string", text: "" }]); - const [partitionValue, setPartitionValue] = useState(); // Defaulting to undefined here is important. It is not the same partition key as "" - const [selectedKey, setSelectedKey] = React.useState({ key: "string", text: "" }); const [formError, setFormError] = useState(""); - const onPartitionKeyChange = (event: React.FormEvent, item: IDropdownOption): void => { - setSelectedKey(item); - }; - const validateUnwrappedParams = (): boolean => { - const unwrappedParams: UnwrappedExecuteSprocParam[] = paramKeyValues; + const unwrappedParams: UnwrappedExecuteSprocParam[] = paramKeyValuesRef.current; for (let i = 0; i < unwrappedParams.length; i++) { const { key: paramType, text: paramValue } = unwrappedParams[i]; if (paramType === "custom" && (paramValue === "" || paramValue === undefined)) { @@ -53,8 +50,9 @@ export const ExecuteSprocParamsPane: FunctionComponent { - const wrappedSprocParams: UnwrappedExecuteSprocParam[] = paramKeyValues; - const { key: partitionKey } = selectedKey; + const wrappedSprocParams: UnwrappedExecuteSprocParam[] = paramKeyValuesRef.current; + const partitionValue: string = partitionValueRef.current; + const partitionKey: string = partitionKeyRef.current; if (partitionKey === "custom" && (partitionValue === "" || partitionValue === undefined)) { setInvalidParamError(partitionValue); return; @@ -78,37 +76,21 @@ export const ExecuteSprocParamsPane: FunctionComponent { - const cloneParamKeyValue = [...paramKeyValues]; - cloneParamKeyValue.splice(indexToRemove, 1); - setParamKeyValues(cloneParamKeyValue); + paramKeyValuesRef.current.splice(indexToRemove, 1); + setNumberOfParams(numberOfParams - 1); }; const addNewParamAtIndex = (indexToAdd: number): void => { - const cloneParamKeyValue = [...paramKeyValues]; - cloneParamKeyValue.splice(indexToAdd, 0, { key: "string", text: "" }); - setParamKeyValues(cloneParamKeyValue); - }; - - const paramValueChange = (value: string, indexOfInput: number): void => { - const cloneParamKeyValue = [...paramKeyValues]; - cloneParamKeyValue[indexOfInput].text = value; - setParamKeyValues(cloneParamKeyValue); - }; - - const paramKeyChange = ( - _event: React.FormEvent, - selectedParam: IDropdownOption, - indexOfParam: number - ): void => { - const cloneParamKeyValue = [...paramKeyValues]; - cloneParamKeyValue[indexOfParam].key = selectedParam.key.toString(); - setParamKeyValues(cloneParamKeyValue); + paramKeyValuesRef.current.splice(indexToAdd, 0, { key: "string", text: "" }); + setNumberOfParams(numberOfParams + 1); }; const addNewParamAtLastIndex = (): void => { - const cloneParamKeyValue = [...paramKeyValues]; - cloneParamKeyValue.splice(cloneParamKeyValue.length, 0, { key: "string", text: "" }); - setParamKeyValues(cloneParamKeyValue); + paramKeyValuesRef.current.push({ + key: "string", + text: "", + }); + setNumberOfParams(numberOfParams + 1); }; const props: RightPaneFormProps = { @@ -118,46 +100,52 @@ export const ExecuteSprocParamsPane: FunctionComponent submit(), }; + const getInputParameterComponent = (): JSX.Element[] => { + const inputParameters: JSX.Element[] = []; + for (let i = 0; i < numberOfParams; i++) { + const paramKeyValue = paramKeyValuesRef.current[i]; + inputParameters.push( + deleteParamAtIndex(i)} + onAddNewParamKeyPress={() => addNewParamAtIndex(i + 1)} + onParamValueChange={(_event, newInput?: string) => (paramKeyValuesRef.current[i].text = newInput)} + onParamKeyChange={(_event, selectedParam: IDropdownOption) => + (paramKeyValuesRef.current[i].key = selectedParam.key.toString()) + } + paramValue={paramKeyValue.text} + selectedKey={paramKeyValue.key} + /> + ); + } + + return inputParameters; + }; + return ( -
-
- { - setPartitionValue(newInput); - }} - onParamKeyChange={onPartitionKeyChange} - paramValue={partitionValue} - selectedKey={selectedKey.key} - /> - {paramKeyValues.map((paramKeyValue, index) => ( - deleteParamAtIndex(index)} - onAddNewParamKeyPress={() => addNewParamAtIndex(index + 1)} - onParamValueChange={(event, newInput?: string) => { - paramValueChange(newInput, index); - }} - onParamKeyChange={(event: React.FormEvent, selectedParam: IDropdownOption) => { - paramKeyChange(event, selectedParam, index); - }} - paramValue={paramKeyValue && paramKeyValue.text} - selectedKey={paramKeyValue && paramKeyValue.key} - /> - ))} - - Add param - Add New Param - -
+
+ (partitionValueRef.current = newInput)} + onParamKeyChange={(_event: React.FormEvent, item: IDropdownOption) => + (partitionKeyRef.current = item.key.toString()) + } + paramValue={partitionValueRef.current} + selectedKey={partitionKeyRef.current} + /> + {getInputParameterComponent()} + addNewParamAtLastIndex()} tabIndex={0}> + Add param + Add New Param +
); diff --git a/src/Explorer/Panes/ExecuteSprocParamsPane/InputParameter.tsx b/src/Explorer/Panes/ExecuteSprocParamsPane/InputParameter.tsx index 677158b61..6fcea9328 100644 --- a/src/Explorer/Panes/ExecuteSprocParamsPane/InputParameter.tsx +++ b/src/Explorer/Panes/ExecuteSprocParamsPane/InputParameter.tsx @@ -55,7 +55,7 @@ export const InputParameter: FunctionComponent = ({ = ({ {isAddRemoveVisible && ( <> diff --git a/src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap b/src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap index 3f163902a..e5bc2cbef 100644 --- a/src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap +++ b/src/Explorer/Panes/ExecuteSprocParamsPane/__snapshots__/ExecuteSprocParamsPane.test.tsx.snap @@ -17,4983 +17,351 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = ` onSubmit={[Function]} >
-
- - - + - - - - -
- - - -
- - - - - - -
-
-
-
- - -
-
- - - - - -
- -
-
-
-
-
-
-
-
- - - - - - - -
- - - -
- - - - - - -
-
-
-
- - -
-
- - - - - -
- -
-
-
-
-
-
- - -
- Delete param -
-
-
-
-
- - -
- Add param -
-
-
-
-
-
-
+ Partition key value + + +
- - -
- Add param -
-
-
- + + + + + + +
+ + + + - - Add New Param - - +
+
+ + + + + +
+ +
+
+
+ +
-
+ + + + + + + + +
+ + + +
+ + + + + + +
+
+
+
+ + +
+
+ + + + + +
+ +
+
+
+
+
+
+ + +
+ Delete param +
+
+
+
+
+ + +
+ Add param +
+
+
+
+
+
+
+ +
+ + +
+ Add param +
+
+
+ + + Add New Param + + +
+
{ const handleOnPageOptionChange = (ev: React.FormEvent, option: IChoiceGroupOption): void => { setPageOption(option.key); }; + + const choiceButtonStyles = { + flexContainer: [ + { + selectors: { + ".ms-ChoiceField-wrapper label": { + fontSize: 12, + paddingTop: 0, + }, + ".ms-ChoiceField": { + marginTop: 0, + }, + }, + }, + ], + }; return (
{shouldShowQueryPageOptions && (
-
-
- Page options +
+ + + Page options + Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page. -
- + +
{isCustomPageOptionSelected() && ( diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap index ccc2ad3a7..4b1d6f079 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap @@ -14,17 +14,24 @@ exports[`Settings Pane should render Default properly 1`] = ` className="settingsSection" >
-
- Page options + + Page options + Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many query results per page. -
+
{ } useSidePanel .getState() - .openSidePanel("New " + getDatabaseName(), ); + .openSidePanel( + "New " + getDatabaseName(), + + ); }, }); } diff --git a/src/Explorer/Tables/Constants.ts b/src/Explorer/Tables/Constants.ts index 9bb313b7a..c9917dcd8 100644 --- a/src/Explorer/Tables/Constants.ts +++ b/src/Explorer/Tables/Constants.ts @@ -19,6 +19,7 @@ export const CassandraType = { Float: "Float", Int: "Int", Text: "Text", + Timestamp: "Timestamp", Uuid: "Uuid", Varchar: "Varchar", Varint: "Varint", diff --git a/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts b/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts index f11730b2b..1f1a90d9d 100644 --- a/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts +++ b/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts @@ -431,7 +431,7 @@ export default class TableEntityListViewModel extends DataTableViewModel { if (newHeaders.length > 0) { // Any new columns found will be added into headers array, which will trigger a re-render of the DataTable. // So there is no need to call it here. - this.updateHeaders(newHeaders, /* notifyColumnChanges */ true); + this.updateHeaders(selectedHeadersUnion, /* notifyColumnChanges */ true); } else { if (columnSortOrder) { this.sortColumns(columnSortOrder, oSettings); diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index afe31b711..62463abfd 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -535,7 +535,8 @@ export class CassandraAPIDataClient extends TableDataClient { dataType === TableConstants.CassandraType.Text || dataType === TableConstants.CassandraType.Inet || dataType === TableConstants.CassandraType.Ascii || - dataType === TableConstants.CassandraType.Varchar + dataType === TableConstants.CassandraType.Varchar || + dataType === TableConstants.CassandraType.Timestamp ); } diff --git a/src/GitHub/GitHubOAuthService.ts b/src/GitHub/GitHubOAuthService.ts index fb9288c7c..7a41076d7 100644 --- a/src/GitHub/GitHubOAuthService.ts +++ b/src/GitHub/GitHubOAuthService.ts @@ -1,8 +1,8 @@ import ko from "knockout"; import postRobot from "post-robot"; +import { GetGithubClientId } from "Utils/GitHubUtils"; import { HttpStatusCodes } from "../Common/Constants"; import { handleError } from "../Common/ErrorHandlingUtils"; -import { configContext } from "../ConfigContext"; import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent"; import { JunoClient } from "../Juno/JunoClient"; import { logConsoleInfo } from "../Utils/NotificationConsoleUtils"; @@ -55,7 +55,7 @@ export class GitHubOAuthService { const params = { scope, - client_id: configContext.GITHUB_CLIENT_ID, + client_id: GetGithubClientId(), redirect_uri: new URL("./connectToGitHub.html", window.location.href).href, state: this.resetState(), }; diff --git a/src/Juno/JunoClient.ts b/src/Juno/JunoClient.ts index 1a43cccc6..540c339e8 100644 --- a/src/Juno/JunoClient.ts +++ b/src/Juno/JunoClient.ts @@ -1,4 +1,5 @@ import ko from "knockout"; +import { GetGithubClientId } from "Utils/GitHubUtils"; import { HttpHeaders, HttpStatusCodes } from "../Common/Constants"; import { configContext } from "../ConfigContext"; import * as DataModels from "../Contracts/DataModels"; @@ -522,7 +523,7 @@ export class JunoClient { private static getGitHubClientParams(): URLSearchParams { const githubParams = new URLSearchParams({ - client_id: configContext.GITHUB_CLIENT_ID, + client_id: GetGithubClientId(), }); if (configContext.GITHUB_CLIENT_SECRET) { diff --git a/src/Shared/Telemetry/TelemetryConstants.ts b/src/Shared/Telemetry/TelemetryConstants.ts index 134340ad4..e9541b9f7 100644 --- a/src/Shared/Telemetry/TelemetryConstants.ts +++ b/src/Shared/Telemetry/TelemetryConstants.ts @@ -50,7 +50,6 @@ export enum Action { SubscriptionSwitch, TenantSwitch, DefaultTenantSwitch, - ResetNotebookWorkspace, CreateNotebookWorkspace, NotebookErrorNotification, CreateSparkCluster, @@ -82,6 +81,8 @@ export enum Action { NotebooksInsertTextCellBelowFromMenu, NotebooksMoveCellUpFromMenu, NotebooksMoveCellDownFromMenu, + PhoenixConnection, + PhoenixResetWorkspace, DeleteCellFromMenu, OpenTerminal, CreateMongoCollectionWithWildcardIndex, diff --git a/src/Utils/GitHubUtils.ts b/src/Utils/GitHubUtils.ts index 13e5f828b..59b14f9a8 100644 --- a/src/Utils/GitHubUtils.ts +++ b/src/Utils/GitHubUtils.ts @@ -1,4 +1,9 @@ // https://github.com///tree/ + +import { JunoEndpoints } from "Common/Constants"; +import { configContext } from "ConfigContext"; +import { userContext } from "UserContext"; + // The url when users visit a repo/branch on github.com export const RepoUriPattern = /https:\/\/github.com\/([^/]*)\/([^/]*)\/tree\/([^?]*)/; @@ -60,3 +65,15 @@ export function toContentUri(owner: string, repo: string, branch: string, path: export function toRawContentUri(owner: string, repo: string, branch: string, path: string): string { return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`; } + +export function GetGithubClientId(): string { + const junoEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT; + if ( + junoEndpoint === JunoEndpoints.Test || + junoEndpoint === JunoEndpoints.Test2 || + junoEndpoint === JunoEndpoints.Test3 + ) { + return configContext.GITHUB_TEST_ENV_CLIENT_ID; + } + return configContext.GITHUB_CLIENT_ID; +}