mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-21 01:41:31 +00:00
Checkpoint
This commit is contained in:
@@ -1,159 +0,0 @@
|
||||
import React from "react";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { AccountSwitchComponent, AccountSwitchComponentProps } from "./AccountSwitchComponent";
|
||||
import { AuthType } from "../../../AuthType";
|
||||
import { DatabaseAccount, Subscription } from "../../../Contracts/DataModels";
|
||||
import { AccountKind } from "../../../Common/Constants";
|
||||
|
||||
const createBlankProps = (): AccountSwitchComponentProps => {
|
||||
return {
|
||||
authType: null,
|
||||
displayText: "",
|
||||
accounts: [],
|
||||
selectedAccountName: null,
|
||||
isLoadingAccounts: false,
|
||||
onAccountChange: jest.fn(),
|
||||
subscriptions: [],
|
||||
selectedSubscriptionId: null,
|
||||
isLoadingSubscriptions: false,
|
||||
onSubscriptionChange: jest.fn()
|
||||
};
|
||||
};
|
||||
|
||||
const createBlankAccount = (): DatabaseAccount => {
|
||||
return {
|
||||
id: "",
|
||||
kind: AccountKind.Default,
|
||||
name: "",
|
||||
properties: null,
|
||||
location: "",
|
||||
tags: null,
|
||||
type: ""
|
||||
};
|
||||
};
|
||||
|
||||
const createBlankSubscription = (): Subscription => {
|
||||
return {
|
||||
subscriptionId: "",
|
||||
displayName: "",
|
||||
authorizationSource: "",
|
||||
state: "",
|
||||
subscriptionPolicies: null,
|
||||
tenantId: "",
|
||||
uniqueDisplayName: ""
|
||||
};
|
||||
};
|
||||
|
||||
const createFullProps = (): AccountSwitchComponentProps => {
|
||||
const props = createBlankProps();
|
||||
props.authType = AuthType.AAD;
|
||||
const account1 = createBlankAccount();
|
||||
account1.name = "account1";
|
||||
const account2 = createBlankAccount();
|
||||
account2.name = "account2";
|
||||
const account3 = createBlankAccount();
|
||||
account3.name = "superlongaccountnamestringtest";
|
||||
props.accounts = [account1, account2, account3];
|
||||
props.selectedAccountName = "account2";
|
||||
|
||||
const sub1 = createBlankSubscription();
|
||||
sub1.displayName = "sub1";
|
||||
sub1.subscriptionId = "a6062a74-5d53-4b20-9545-000b95f22297";
|
||||
const sub2 = createBlankSubscription();
|
||||
sub2.displayName = "subsubsubsubsubsubsub2";
|
||||
sub2.subscriptionId = "b20b3e93-0185-4326-8a9c-d44bac276b6b";
|
||||
props.subscriptions = [sub1, sub2];
|
||||
props.selectedSubscriptionId = "a6062a74-5d53-4b20-9545-000b95f22297";
|
||||
|
||||
return props;
|
||||
};
|
||||
|
||||
describe("test render", () => {
|
||||
it("renders no auth type -> handle error in code", () => {
|
||||
const props = createBlankProps();
|
||||
|
||||
const wrapper = shallow(<AccountSwitchComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// Encrypted Token
|
||||
it("renders auth security token, with selected account name", () => {
|
||||
const props = createBlankProps();
|
||||
props.authType = AuthType.EncryptedToken;
|
||||
props.selectedAccountName = "testaccount";
|
||||
|
||||
const wrapper = shallow(<AccountSwitchComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// AAD
|
||||
it("renders auth aad, with all information", () => {
|
||||
const props = createFullProps();
|
||||
const wrapper = shallow(<AccountSwitchComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders auth aad all dropdown menus", () => {
|
||||
const props = createFullProps();
|
||||
const wrapper = mount(<AccountSwitchComponent {...props} />);
|
||||
|
||||
expect(wrapper.exists("div.accountSwitchContextualMenu")).toBe(false);
|
||||
wrapper.find("button.accountSwitchButton").simulate("click");
|
||||
expect(wrapper.exists("div.accountSwitchContextualMenu")).toBe(true);
|
||||
|
||||
expect(wrapper.exists("div.accountSwitchSubscriptionDropdown")).toBe(true);
|
||||
wrapper.find("DropdownBase.accountSwitchSubscriptionDropdown").simulate("click");
|
||||
// Click will dismiss the first contextual menu in enzyme. Need to dig deeper to achieve below test
|
||||
|
||||
// expect(wrapper.exists("div.accountSwitchSubscriptionDropdownMenu")).toBe(true);
|
||||
// expect(wrapper.find("button.ms-Dropdown-item").length).toBe(2);
|
||||
// wrapper.find("div.accountSwitchSubscriptionDropdown").simulate("click");
|
||||
// expect(wrapper.exists("div.accountSwitchSubscriptionDropdownMenu")).toBe(false);
|
||||
|
||||
// expect(wrapper.exists("div.accountSwitchAccountDropdown")).toBe(true);
|
||||
// wrapper.find("div.accountSwitchAccountDropdown").simulate("click");
|
||||
// expect(wrapper.exists("div.accountSwitchAccountDropdownMenu")).toBe(true);
|
||||
// expect(wrapper.find("button.ms-Dropdown-item").length).toBe(3);
|
||||
// wrapper.find("div.accountSwitchAccountDropdown").simulate("click");
|
||||
// expect(wrapper.exists("div.accountSwitchAccountDropdownMenu")).toBe(false);
|
||||
|
||||
// wrapper.find("button.accountSwitchButton").simulate("click");
|
||||
// expect(wrapper.exists("div.accountSwitchContextualMenu")).toBe(false);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
// describe("test function", () => {
|
||||
// it("switch subscription function", () => {
|
||||
// const props = createFullProps();
|
||||
// const wrapper = mount(<AccountSwitchComponent {...props} />);
|
||||
|
||||
// wrapper.find("button.accountSwitchButton").simulate("click");
|
||||
// wrapper.find("div.accountSwitchSubscriptionDropdown").simulate("click");
|
||||
// wrapper
|
||||
// .find("button.ms-Dropdown-item")
|
||||
// .at(1)
|
||||
// .simulate("click");
|
||||
// expect(props.onSubscriptionChange).toBeCalled();
|
||||
// expect(props.onSubscriptionChange).toHaveBeenCalled();
|
||||
|
||||
// wrapper.unmount();
|
||||
// });
|
||||
|
||||
// it("switch account", () => {
|
||||
// const props = createFullProps();
|
||||
// const wrapper = mount(<AccountSwitchComponent {...props} />);
|
||||
|
||||
// wrapper.find("button.accountSwitchButton").simulate("click");
|
||||
// wrapper.find("div.accountSwitchAccountDropdown").simulate("click");
|
||||
// wrapper
|
||||
// .find("button.ms-Dropdown-item")
|
||||
// .at(0)
|
||||
// .simulate("click");
|
||||
// expect(props.onAccountChange).toBeCalled();
|
||||
// expect(props.onAccountChange).toHaveBeenCalled();
|
||||
|
||||
// wrapper.unmount();
|
||||
// });
|
||||
// });
|
||||
@@ -1,5 +1,4 @@
|
||||
import { StyleConstants } from "../../../Common/Constants";
|
||||
|
||||
import * as React from "react";
|
||||
import { DefaultButton, IButtonStyles, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
||||
import { IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||
@@ -38,10 +37,10 @@ const buttonStyles: IButtonStyles = {
|
||||
}
|
||||
};
|
||||
|
||||
export const AccountSwitchComponent: React.FunctionComponent = () => {
|
||||
const subscriptions = useSubscriptions();
|
||||
export const AccountSwitchComponent: React.FunctionComponent<{ armToken: string }> = ({ armToken }) => {
|
||||
const subscriptions = useSubscriptions(armToken);
|
||||
const [selectedSubscriptionId, setSelectedSubscriptionId] = React.useState<string>();
|
||||
const accounts = useDatabaseAccounts(selectedSubscriptionId);
|
||||
const accounts = useDatabaseAccounts(selectedSubscriptionId, armToken);
|
||||
const [selectedAccountName, setSelectedAccoutName] = React.useState<string>();
|
||||
|
||||
const menuProps: IContextualMenuProps = {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
import { AccountSwitchComponent, AccountSwitchComponentProps } from "./AccountSwitchComponent";
|
||||
|
||||
export class AccountSwitchComponentAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<AccountSwitchComponentProps>;
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
return <AccountSwitchComponent {...this.parameters()} />;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`test render renders auth aad, with all information 1`] = `
|
||||
<CustomizedDefaultButton
|
||||
className="accountSwitchButton"
|
||||
id="accountSwitchButton"
|
||||
menuProps={
|
||||
Object {
|
||||
"className": "accountSwitchContextualMenu",
|
||||
"directionalHintFixed": true,
|
||||
"items": Array [
|
||||
Object {
|
||||
"key": "switchSubscription",
|
||||
"onRender": [Function],
|
||||
},
|
||||
Object {
|
||||
"key": "switchAccount",
|
||||
"onRender": [Function],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"backgroundColor": undefined,
|
||||
"color": undefined,
|
||||
"fontSize": undefined,
|
||||
"height": 40,
|
||||
"marginRight": 5,
|
||||
"padding": 0,
|
||||
"paddingLeft": 10,
|
||||
},
|
||||
"rootExpanded": Object {
|
||||
"backgroundColor": undefined,
|
||||
"color": undefined,
|
||||
},
|
||||
"rootFocused": Object {
|
||||
"backgroundColor": undefined,
|
||||
"color": undefined,
|
||||
},
|
||||
"rootHovered": Object {
|
||||
"backgroundColor": undefined,
|
||||
"color": undefined,
|
||||
},
|
||||
"rootPressed": Object {
|
||||
"backgroundColor": undefined,
|
||||
"color": undefined,
|
||||
},
|
||||
"textContainer": Object {
|
||||
"flexGrow": "initial",
|
||||
},
|
||||
}
|
||||
}
|
||||
text="account2"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`test render renders auth security token, with selected account name 1`] = `
|
||||
<span
|
||||
className="accountNameHeader"
|
||||
>
|
||||
testaccount
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`test render renders no auth type -> handle error in code 1`] = `
|
||||
<span
|
||||
className="accountNameHeader"
|
||||
/>
|
||||
`;
|
||||
@@ -2,7 +2,7 @@ import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { ConnectionStringParser } from "../../Platform/Hosted/Helpers/ConnectionStringParser";
|
||||
import { parseConnectionString } from "../../Platform/Hosted/Helpers/ConnectionStringParser";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||
@@ -48,9 +48,7 @@ export class RenewAdHocAccessPane extends ContextualPaneBase {
|
||||
};
|
||||
|
||||
private _shouldShowContextSwitchPrompt(): boolean {
|
||||
const inputMetadata: DataModels.AccessInputMetadata = ConnectionStringParser.parseConnectionString(
|
||||
this.accessKey()
|
||||
);
|
||||
const inputMetadata: DataModels.AccessInputMetadata = parseConnectionString(this.accessKey());
|
||||
const apiKind: DataModels.ApiKind =
|
||||
this.container && DefaultExperienceUtility.getApiKindFromDefaultExperience(this.container.defaultExperience());
|
||||
const hasOpenedTabs: boolean =
|
||||
|
||||
@@ -2,8 +2,6 @@ import "./Shared/appInsights";
|
||||
import * as _ from "underscore";
|
||||
import * as ko from "knockout";
|
||||
import hasher from "hasher";
|
||||
import { AccountSwitchComponentProps } from "./Explorer/Controls/AccountSwitch/AccountSwitchComponent";
|
||||
import { AccountSwitchComponentAdapter } from "./Explorer/Controls/AccountSwitch/AccountSwitchComponentAdapter";
|
||||
import { Action } from "./Shared/Telemetry/TelemetryConstants";
|
||||
import { ArmResourceUtils } from "./Platform/Hosted/ArmResourceUtils";
|
||||
import AuthHeadersUtil from "./Platform/Hosted/Authorization";
|
||||
@@ -50,13 +48,11 @@ class HostedExplorer {
|
||||
public firewallWarningComponentAdapter: DialogComponentAdapter;
|
||||
public dialogComponentAdapter: DialogComponentAdapter;
|
||||
public meControlComponentAdapter: MeControlComponentAdapter;
|
||||
public accountSwitchComponentAdapter: AccountSwitchComponentAdapter;
|
||||
public switchDirectoryPane: SwitchDirectoryPane;
|
||||
|
||||
private _firewallWarningDialogProps: ko.Observable<DialogProps>;
|
||||
private _dialogProps: ko.Observable<DialogProps>;
|
||||
private _meControlProps: ko.Observable<MeControlComponentProps>;
|
||||
private _accountSwitchProps: ko.Observable<AccountSwitchComponentProps>;
|
||||
private _controlbarCommands: ko.ObservableArray<CommandButtonComponentProps>;
|
||||
private _directoryDropdownProps: ko.Observable<DefaultDirectoryDropdownProps>;
|
||||
private _directoryListProps: ko.Observable<DirectoryListProps>;
|
||||
@@ -161,35 +157,10 @@ class HostedExplorer {
|
||||
this.meControlComponentAdapter = new MeControlComponentAdapter();
|
||||
this.meControlComponentAdapter.parameters = this._meControlProps;
|
||||
|
||||
this._accountSwitchProps = ko.observable<AccountSwitchComponentProps>({
|
||||
authType: AuthType.EncryptedToken,
|
||||
selectedAccountName: "",
|
||||
accounts: [],
|
||||
isLoadingAccounts: false,
|
||||
onAccountChange: this._onAccountChange,
|
||||
selectedSubscriptionId: undefined,
|
||||
subscriptions: [],
|
||||
isLoadingSubscriptions: false,
|
||||
onSubscriptionChange: this._onSubscriptionChange
|
||||
});
|
||||
this.accountSwitchComponentAdapter = new AccountSwitchComponentAdapter();
|
||||
this.accountSwitchComponentAdapter.parameters = this._accountSwitchProps;
|
||||
|
||||
this.isAccountActive = ko.computed<boolean>(() => {
|
||||
if (
|
||||
this._accountSwitchProps() &&
|
||||
(this._accountSwitchProps().displayText || this._accountSwitchProps().selectedAccountName)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
hasher.initialized.add(updateExplorerHash);
|
||||
hasher.changed.add(updateExplorerHash);
|
||||
hasher.init();
|
||||
window.addEventListener("message", this._handleMessage.bind(this), false);
|
||||
this._handleAadLogin();
|
||||
}
|
||||
|
||||
public explorer_click() {
|
||||
@@ -348,43 +319,10 @@ class HostedExplorer {
|
||||
this._meControlProps(propsToUpdate);
|
||||
}
|
||||
|
||||
private _updateAccountSwitchProps(props: Partial<AccountSwitchComponentProps>) {
|
||||
private _updateAccountSwitchProps(props: any) {
|
||||
if (!props) {
|
||||
return;
|
||||
}
|
||||
|
||||
const propsToUpdate = this._accountSwitchProps();
|
||||
if (props.authType) {
|
||||
if (props.selectedAccountName != null) {
|
||||
propsToUpdate.selectedAccountName = props.selectedAccountName;
|
||||
}
|
||||
if (props.authType === AuthType.EncryptedToken) {
|
||||
propsToUpdate.authType = AuthType.EncryptedToken;
|
||||
} else if (props.authType === AuthType.AAD) {
|
||||
propsToUpdate.authType = AuthType.AAD;
|
||||
if (props.displayText != null) {
|
||||
propsToUpdate.displayText = props.displayText;
|
||||
}
|
||||
if (props.isLoadingAccounts != null) {
|
||||
propsToUpdate.isLoadingAccounts = props.isLoadingAccounts;
|
||||
}
|
||||
if (props.accounts) {
|
||||
propsToUpdate.accounts = props.accounts.sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||
}
|
||||
|
||||
if (props.isLoadingSubscriptions != null) {
|
||||
propsToUpdate.isLoadingSubscriptions = props.isLoadingSubscriptions;
|
||||
}
|
||||
if (props.subscriptions) {
|
||||
propsToUpdate.subscriptions = props.subscriptions.sort((a, b) => (a.displayName < b.displayName ? -1 : 1));
|
||||
}
|
||||
if (props.selectedSubscriptionId != null) {
|
||||
propsToUpdate.selectedSubscriptionId = props.selectedSubscriptionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._accountSwitchProps(propsToUpdate);
|
||||
}
|
||||
|
||||
private _onAccountChange = (newAccount: DatabaseAccount) => {
|
||||
@@ -522,36 +460,6 @@ class HostedExplorer {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleAadLogin() {
|
||||
AuthHeadersUtil.processTokenResponse();
|
||||
if (AuthHeadersUtil.isUserSignedIn()) {
|
||||
window.authType = AuthType.AAD;
|
||||
const user = AuthHeadersUtil.getCachedUser();
|
||||
this._updateMeControlProps({
|
||||
isUserSignedIn: true,
|
||||
user: {
|
||||
name: user.profile.name,
|
||||
email: user.userName,
|
||||
tenantName: undefined,
|
||||
imageUrl: undefined
|
||||
}
|
||||
});
|
||||
|
||||
AuthHeadersUtil.getPhotoFromGraphAPI().then(blob => {
|
||||
const imageUrl = URL.createObjectURL(blob);
|
||||
this._updateMeControlProps({
|
||||
isUserSignedIn: true,
|
||||
user: {
|
||||
name: undefined,
|
||||
email: undefined,
|
||||
tenantName: undefined,
|
||||
imageUrl: imageUrl
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _handleGetAccessAadRequest() {
|
||||
this._getAccessAad().then(
|
||||
response => {
|
||||
|
||||
@@ -2,23 +2,19 @@ import "./Platform/Hosted/ConnectScreen.less";
|
||||
import { useBoolean } from "@uifabric/react-hooks";
|
||||
import {
|
||||
DefaultButton,
|
||||
DetailsList,
|
||||
DirectionalHint,
|
||||
FocusZone,
|
||||
IContextualMenuProps,
|
||||
initializeIcons,
|
||||
Panel,
|
||||
PanelType,
|
||||
Persona,
|
||||
PersonaInitialsColor,
|
||||
PersonaSize,
|
||||
SelectionMode,
|
||||
Selection
|
||||
ChoiceGroup
|
||||
} from "office-ui-fabric-react";
|
||||
import * as React from "react";
|
||||
import { render } from "react-dom";
|
||||
import FeedbackIcon from "../images/Feedback.svg";
|
||||
import ConnectIcon from "../images/HostedConnectwhite.svg";
|
||||
import ChevronRight from "../images/chevron-right.svg";
|
||||
import "../less/hostedexplorer.less";
|
||||
import { CommandButtonComponent } from "./Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||
@@ -26,111 +22,74 @@ import "./Explorer/Menus/NavBar/MeControlComponent.less";
|
||||
import { useGraphPhoto } from "./hooks/useGraphPhoto";
|
||||
import "./Shared/appInsights";
|
||||
import { AccountSwitchComponent } from "./Explorer/Controls/AccountSwitch/AccountSwitchComponent";
|
||||
import { AuthContext, AuthProvider } from "./contexts/authContext";
|
||||
import { usePortalAccessToken } from "./hooks/usePortalAccessToken";
|
||||
import { useDirectories } from "./hooks/useDirectories";
|
||||
import * as Msal from "msal";
|
||||
import { configContext } from "./ConfigContext";
|
||||
import { HttpHeaders } from "./Common/Constants";
|
||||
import { GenerateTokenResponse } from "./Contracts/DataModels";
|
||||
import { AuthType } from "./AuthType";
|
||||
|
||||
initializeIcons();
|
||||
|
||||
const msal = new Msal.UserAgentApplication({
|
||||
auth: {
|
||||
authority: "https://login.microsoft.com/common",
|
||||
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
|
||||
redirectUri: "https://dataexplorer-dev.azurewebsites.net" // TODO! This should only be set in development
|
||||
}
|
||||
});
|
||||
|
||||
const App: React.FunctionComponent = () => {
|
||||
// Hooks for handling encrypted portal tokens
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const encryptedToken = params && params.get("key");
|
||||
const [encryptedToken, setEncryptedToken] = React.useState<string>(params && params.get("key"));
|
||||
const encryptedTokenMetadata = usePortalAccessToken(encryptedToken);
|
||||
|
||||
// Hooks for showing/hiding UI
|
||||
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
|
||||
const { isLoggedIn, aadlogin: login, account, aadlogout: logout, tenantId } = React.useContext(AuthContext);
|
||||
const [isConnectionStringVisible, { setTrue: showConnectionString }] = useBoolean(false);
|
||||
const photo = useGraphPhoto();
|
||||
const directories = useDirectories();
|
||||
// const [selectedItem, setSelectedItem] = React.useState<any>(undefined);
|
||||
const selection = new Selection({
|
||||
getKey: item => item.tenantId,
|
||||
items: directories,
|
||||
onSelectionChanged: () => {
|
||||
const selected = selection.getSelection()[0];
|
||||
if (selected.tenantId !== tenantId) {
|
||||
console.log("new Tenant", selected.tenantId);
|
||||
}
|
||||
},
|
||||
selectionMode: SelectionMode.single
|
||||
});
|
||||
selection.setKeySelected(tenantId, true, false);
|
||||
|
||||
// private _renderPersonaComponent = (): JSX.Element => {
|
||||
// const { user } = this.props;
|
||||
// const personaProps: IPersonaSharedProps = {
|
||||
// imageUrl: user.imageUrl,
|
||||
// text: user.name,
|
||||
// secondaryText: user.email,
|
||||
// showSecondaryText: true,
|
||||
// showInitialsUntilImageLoads: true,
|
||||
// initialsColor: PersonaInitialsColor.teal,
|
||||
// size: PersonaSize.size72,
|
||||
// className: "mecontrolContextualMenuPersona"
|
||||
// };
|
||||
// Hooks for AAD authentication
|
||||
const [isLoggedIn, { setTrue: setLoggedIn, setFalse: setLoggedOut }] = useBoolean(false);
|
||||
const [account, setAccount] = React.useState<Msal.Account>();
|
||||
const [graphToken, setGraphToken] = React.useState<string>();
|
||||
const [armToken, setArmToken] = React.useState<string>();
|
||||
const [tenantId, setTenantId] = React.useState<string>();
|
||||
const [connectionString, setConnectionString] = React.useState<string>("");
|
||||
|
||||
// return <Persona {...personaProps} />;
|
||||
// };
|
||||
const login = React.useCallback(async () => {
|
||||
const response = await msal.loginPopup();
|
||||
setLoggedIn();
|
||||
setAccount(response.account);
|
||||
setTenantId(response.tenantId);
|
||||
}, []);
|
||||
|
||||
const menuProps: IContextualMenuProps = {
|
||||
className: "mecontrolContextualMenu",
|
||||
isBeakVisible: false,
|
||||
directionalHintFixed: true,
|
||||
directionalHint: DirectionalHint.bottomRightEdge,
|
||||
calloutProps: {
|
||||
minPagePadding: 0
|
||||
},
|
||||
items: [
|
||||
{
|
||||
key: "SwitchDirectory",
|
||||
text: "Switch Directory",
|
||||
onClick: openPanel
|
||||
},
|
||||
{
|
||||
key: "SignOut",
|
||||
text: "Sign Out",
|
||||
onClick: logout
|
||||
}
|
||||
]
|
||||
};
|
||||
const logout = React.useCallback(() => {
|
||||
msal.logout();
|
||||
setLoggedOut();
|
||||
}, []);
|
||||
|
||||
// {
|
||||
// id: "commandbutton-settings",
|
||||
// iconSrc: SettingsIcon,
|
||||
// iconAlt: "setting button",
|
||||
// onCommandClick: () => {},
|
||||
// commandButtonLabel: undefined,
|
||||
// ariaLabel: "setting button",
|
||||
// tooltipText: "Global settings",
|
||||
// hasPopup: true,
|
||||
// disabled: false
|
||||
// },
|
||||
// {
|
||||
// id: "commandbutton-feedback",
|
||||
// iconSrc: FeedbackIcon,
|
||||
// iconAlt: "feeback button",
|
||||
// onCommandClick: () =>
|
||||
// window.open(
|
||||
// "https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback"
|
||||
// ),
|
||||
// commandButtonLabel: undefined,
|
||||
// ariaLabel: "feeback button",
|
||||
// tooltipText: "Send feedback",
|
||||
// hasPopup: true,
|
||||
// disabled: false
|
||||
// }
|
||||
|
||||
const buttonProps = {
|
||||
id: "mecontrolHeader",
|
||||
className: "mecontrolHeaderButton",
|
||||
menuProps,
|
||||
styles: {
|
||||
rootHovered: { backgroundColor: "#393939" },
|
||||
rootFocused: { backgroundColor: "#393939" },
|
||||
rootPressed: { backgroundColor: "#393939" },
|
||||
rootExpanded: { backgroundColor: "#393939" }
|
||||
React.useEffect(() => {
|
||||
if (account && tenantId) {
|
||||
console.log(msal.authority);
|
||||
console.log("Getting tokens for", tenantId);
|
||||
Promise.all([
|
||||
msal.acquireTokenSilent({
|
||||
scopes: ["https://graph.windows.net//.default"]
|
||||
}),
|
||||
msal.acquireTokenSilent({
|
||||
scopes: ["https://management.azure.com//.default"]
|
||||
})
|
||||
]).then(([graphTokenResponse, armTokenResponse]) => {
|
||||
setGraphToken(graphTokenResponse.accessToken);
|
||||
setArmToken(armTokenResponse.accessToken);
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [account, tenantId]);
|
||||
|
||||
const photo = useGraphPhoto(graphToken);
|
||||
const directories = useDirectories(armToken);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -151,7 +110,7 @@ const App: React.FunctionComponent = () => {
|
||||
)}
|
||||
{isLoggedIn && (
|
||||
<span className="accountSwitchComponentContainer">
|
||||
<AccountSwitchComponent />
|
||||
<AccountSwitchComponent armToken={armToken} />
|
||||
</span>
|
||||
)}
|
||||
{!isLoggedIn && encryptedTokenMetadata?.accountName && (
|
||||
@@ -161,18 +120,6 @@ const App: React.FunctionComponent = () => {
|
||||
)}
|
||||
</div>
|
||||
<div className="feedbackConnectSettingIcons">
|
||||
{isLoggedIn && (
|
||||
<CommandButtonComponent
|
||||
id="commandbutton-connect"
|
||||
iconSrc={ConnectIcon}
|
||||
iconAlt="connect button"
|
||||
onCommandClick={() => {}}
|
||||
ariaLabel="connect button"
|
||||
tooltipText="Connect to a Cosmos DB account"
|
||||
hasPopup={true}
|
||||
disabled={false}
|
||||
/>
|
||||
)}
|
||||
<CommandButtonComponent
|
||||
id="commandbutton-feedback"
|
||||
iconSrc={FeedbackIcon}
|
||||
@@ -189,7 +136,37 @@ const App: React.FunctionComponent = () => {
|
||||
<div className="meControl">
|
||||
{isLoggedIn ? (
|
||||
<FocusZone>
|
||||
<DefaultButton {...buttonProps}>
|
||||
<DefaultButton
|
||||
id="mecontrolHeader"
|
||||
className="mecontrolHeaderButton"
|
||||
menuProps={{
|
||||
className: "mecontrolContextualMenu",
|
||||
isBeakVisible: false,
|
||||
directionalHintFixed: true,
|
||||
directionalHint: DirectionalHint.bottomRightEdge,
|
||||
calloutProps: {
|
||||
minPagePadding: 0
|
||||
},
|
||||
items: [
|
||||
{
|
||||
key: "SwitchDirectory",
|
||||
text: "Switch Directory",
|
||||
onClick: openPanel
|
||||
},
|
||||
{
|
||||
key: "SignOut",
|
||||
text: "Sign Out",
|
||||
onClick: logout
|
||||
}
|
||||
]
|
||||
}}
|
||||
styles={{
|
||||
rootHovered: { backgroundColor: "#393939" },
|
||||
rootFocused: { backgroundColor: "#393939" },
|
||||
rootPressed: { backgroundColor: "#393939" },
|
||||
rootExpanded: { backgroundColor: "#393939" }
|
||||
}}
|
||||
>
|
||||
<Persona
|
||||
imageUrl={photo}
|
||||
text={account?.name}
|
||||
@@ -228,7 +205,7 @@ const App: React.FunctionComponent = () => {
|
||||
)}&metadata=${JSON.stringify(encryptedTokenMetadata)}`}
|
||||
></iframe>
|
||||
)}
|
||||
{!encryptedTokenMetadata && isLoggedIn && (
|
||||
{/* {!encryptedTokenMetadata && isLoggedIn && (
|
||||
<iframe
|
||||
id="explorerMenu"
|
||||
name="explorer"
|
||||
@@ -236,7 +213,7 @@ const App: React.FunctionComponent = () => {
|
||||
title="explorer"
|
||||
src={`explorer.html?v=1.0.1&platform=Hosted&authType=${AuthType.AAD}`}
|
||||
></iframe>
|
||||
)}
|
||||
)} */}
|
||||
{!isLoggedIn && !encryptedTokenMetadata && (
|
||||
<div id="connectExplorer" className="connectExplorerContainer" style={{ display: "flex" }}>
|
||||
<div className="connectExplorerFormContainer">
|
||||
@@ -246,15 +223,42 @@ const App: React.FunctionComponent = () => {
|
||||
</p>
|
||||
<p className="welcomeText">Welcome to Azure Cosmos DB</p>
|
||||
{isConnectionStringVisible ? (
|
||||
<form id="connectWithConnectionString">
|
||||
<form
|
||||
id="connectWithConnectionString"
|
||||
onSubmit={async event => {
|
||||
event.preventDefault();
|
||||
// const foo = parseConnectionString(connectionString);
|
||||
const headers = new Headers();
|
||||
headers.append(HttpHeaders.connectionString, connectionString);
|
||||
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
|
||||
const response = await fetch(url, { headers, method: "POST" });
|
||||
if (!response.ok) {
|
||||
throw response;
|
||||
}
|
||||
// This API has a quirk where it must be parsed twice
|
||||
const result: GenerateTokenResponse = JSON.parse(await response.json());
|
||||
console.log(result.readWrite || result.read);
|
||||
setEncryptedToken(decodeURIComponent(result.readWrite || result.read));
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<p className="connectExplorerContent connectStringText">
|
||||
Connect to your account with connection string
|
||||
</p>
|
||||
<p className="connectExplorerContent">
|
||||
<input className="inputToken" type="text" required placeholder="Please enter a connection string" />
|
||||
<input
|
||||
className="inputToken"
|
||||
type="text"
|
||||
required
|
||||
placeholder="Please enter a connection string"
|
||||
value={connectionString}
|
||||
onChange={event => {
|
||||
setConnectionString(event.target.value);
|
||||
}}
|
||||
/>
|
||||
<span className="errorDetailsInfoTooltip" style={{ display: "none" }}>
|
||||
<img className="errorImg" src="images/error.svg" alt="Error notification" />
|
||||
<span className="errorDetails" />
|
||||
<span className="errorDetails"></span>
|
||||
</span>
|
||||
</p>
|
||||
<p className="connectExplorerContent">
|
||||
@@ -285,35 +289,30 @@ const App: React.FunctionComponent = () => {
|
||||
onDismiss={dismissPanel}
|
||||
closeButtonAriaLabel="Close"
|
||||
>
|
||||
<DetailsList
|
||||
items={selection.getItems()}
|
||||
columns={[
|
||||
{
|
||||
key: "name",
|
||||
name: "Name",
|
||||
minWidth: 200,
|
||||
maxWidth: 200,
|
||||
fieldName: "displayName"
|
||||
},
|
||||
{
|
||||
key: "id",
|
||||
name: "ID",
|
||||
minWidth: 200,
|
||||
maxWidth: 200,
|
||||
fieldName: "tenantId"
|
||||
}
|
||||
]}
|
||||
selectionMode={SelectionMode.single}
|
||||
selection={selection}
|
||||
<ChoiceGroup
|
||||
options={directories.map(dir => ({ key: dir.tenantId, text: `${dir.displayName} (${dir.tenantId})` }))}
|
||||
selectedKey={tenantId}
|
||||
onChange={async () => {
|
||||
dismissPanel();
|
||||
// TODO!!! This does not work. Still not sure why. Tried lots of stuff.
|
||||
// const response = await msal.loginPopup({
|
||||
// authority: `https://login.microsoftonline.com/${option.key}`
|
||||
// });
|
||||
// // msal = new Msal.UserAgentApplication({
|
||||
// // auth: {
|
||||
// // authority: `https://login.microsoftonline.com/${option.key}`,
|
||||
// // clientId: "203f1145-856a-4232-83d4-a43568fba23d",
|
||||
// // redirectUri: "https://dataexplorer-dev.azurewebsites.net" // TODO! This should only be set in development
|
||||
// // }
|
||||
// // });
|
||||
// setTenantId(option.key);
|
||||
// setAccount(response.account);
|
||||
// console.log(account);
|
||||
}}
|
||||
/>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthProvider>
|
||||
<App />
|
||||
</AuthProvider>,
|
||||
document.body
|
||||
);
|
||||
render(<App />, document.body);
|
||||
|
||||
@@ -55,11 +55,6 @@ import "url-polyfill/url-polyfill.min";
|
||||
|
||||
initializeIcons();
|
||||
|
||||
import * as ko from "knockout";
|
||||
import * as TelemetryProcessor from "./Shared/Telemetry/TelemetryProcessor";
|
||||
import { Action, ActionModifiers } from "./Shared/Telemetry/TelemetryConstants";
|
||||
|
||||
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";
|
||||
|
||||
@@ -107,14 +107,6 @@ export default class AuthHeadersUtil {
|
||||
const user = AuthHeadersUtil._authContext.getCachedUser();
|
||||
return !!user;
|
||||
}
|
||||
|
||||
public static getCachedUser(): AuthenticationContext.UserInfo {
|
||||
if (this.isUserSignedIn()) {
|
||||
return AuthHeadersUtil._authContext.getCachedUser();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static signIn() {
|
||||
if (!AuthHeadersUtil.isUserSignedIn()) {
|
||||
AuthHeadersUtil._authContext.login();
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import AuthHeadersUtil from "../Platform/Hosted/Authorization";
|
||||
import * as UserUtils from "./UserUtils";
|
||||
|
||||
describe("UserUtils", () => {
|
||||
it("getFullName works in regular data explorer (inside portal)", () => {
|
||||
const user: AuthenticationContext.UserInfo = {
|
||||
userName: "userName",
|
||||
profile: {
|
||||
name: "name"
|
||||
}
|
||||
};
|
||||
AuthHeadersUtil.getCachedUser = jest.fn().mockReturnValue(user);
|
||||
|
||||
expect(UserUtils.getFullName()).toBe("name");
|
||||
});
|
||||
|
||||
it("getFullName works in fullscreen data explorer (outside portal)", () => {
|
||||
jest.mock("./AuthorizationUtils", () => {
|
||||
(): { name: string } => ({ name: "name" });
|
||||
});
|
||||
|
||||
expect(UserUtils.getFullName()).toBe("name");
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,8 @@
|
||||
import AuthHeadersUtil from "../Platform/Hosted/Authorization";
|
||||
import { decryptJWTToken } from "./AuthorizationUtils";
|
||||
import { userContext } from "../UserContext";
|
||||
|
||||
export function getFullName(): string {
|
||||
let fullName: string;
|
||||
const user = AuthHeadersUtil.getCachedUser();
|
||||
if (user) {
|
||||
fullName = user.profile.name;
|
||||
} else {
|
||||
const authToken = userContext.authorizationToken;
|
||||
const props = decryptJWTToken(authToken);
|
||||
fullName = props.name;
|
||||
}
|
||||
|
||||
return fullName;
|
||||
const authToken = userContext.authorizationToken;
|
||||
const props = decryptJWTToken(authToken);
|
||||
return props.name;
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import * as React from "react";
|
||||
import * as Msal from "msal";
|
||||
import { createContext, useState, useCallback } from "react";
|
||||
import { useBoolean } from "@uifabric/react-hooks";
|
||||
|
||||
const defaultError = "Auth context method was called witout a AuthProvider component in the component tree";
|
||||
|
||||
const msal = new Msal.UserAgentApplication({
|
||||
auth: {
|
||||
authority: "https://login.microsoft.com/common",
|
||||
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
|
||||
redirectUri: "https://dataexplorer-dev.azurewebsites.net" // TODO! This should only be set development
|
||||
}
|
||||
});
|
||||
|
||||
interface AuthContext {
|
||||
isLoggedIn: boolean;
|
||||
account?: Msal.Account;
|
||||
graphToken?: string;
|
||||
armToken?: string;
|
||||
tenantId?: string;
|
||||
aadlogout: () => unknown;
|
||||
aadlogin: () => unknown;
|
||||
}
|
||||
|
||||
export const AuthContext = createContext<AuthContext>({
|
||||
isLoggedIn: false,
|
||||
aadlogin: () => {
|
||||
throw Error(defaultError);
|
||||
},
|
||||
aadlogout: () => {
|
||||
throw Error(defaultError);
|
||||
}
|
||||
});
|
||||
|
||||
export const AuthProvider: React.FunctionComponent = ({ children }) => {
|
||||
const [isLoggedIn, { setTrue: setLoggedIn, setFalse: setLoggedOut }] = useBoolean(false);
|
||||
const [account, setAccount] = useState<Msal.Account>();
|
||||
const [graphToken, setGraphToken] = useState<string>();
|
||||
const [armToken, setArmToken] = useState<string>();
|
||||
const [tenantId, setTenantId] = useState<string>();
|
||||
|
||||
const aadlogin = useCallback(async () => {
|
||||
const response = await msal.loginPopup();
|
||||
setLoggedIn();
|
||||
setAccount(response.account);
|
||||
setTenantId(response.tenantId);
|
||||
msal.authority = "https://login.microsoftonline.com/481f23b0-3fb3-4e76-812d-15513d11dbfc";
|
||||
|
||||
const [graphTokenResponse, armTokenResponse] = await Promise.all([
|
||||
msal.acquireTokenSilent({
|
||||
scopes: ["https://graph.windows.net//.default"]
|
||||
}),
|
||||
msal.acquireTokenSilent({
|
||||
scopes: ["https://management.azure.com//.default"]
|
||||
})
|
||||
]);
|
||||
|
||||
setGraphToken(graphTokenResponse.accessToken);
|
||||
setArmToken(armTokenResponse.accessToken);
|
||||
}, []);
|
||||
|
||||
const aadlogout = useCallback(() => {
|
||||
msal.logout();
|
||||
setLoggedOut();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ isLoggedIn, account, aadlogin, aadlogout, graphToken, armToken, tenantId }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AuthContext } from "../contexts/authContext";
|
||||
import { useEffect, useState } from "react";
|
||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||
|
||||
interface AccountListResult {
|
||||
@@ -39,8 +38,7 @@ export async function fetchDatabaseAccounts(
|
||||
return accounts;
|
||||
}
|
||||
|
||||
export function useDatabaseAccounts(subscriptionId: string): DatabaseAccount[] {
|
||||
const { armToken } = useContext(AuthContext);
|
||||
export function useDatabaseAccounts(subscriptionId: string, armToken: string): DatabaseAccount[] {
|
||||
const [state, setState] = useState<DatabaseAccount[]>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AuthContext } from "../contexts/authContext";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Tenant } from "../Contracts/DataModels";
|
||||
|
||||
interface TenantListResult {
|
||||
@@ -29,8 +28,7 @@ export async function fetchDirectories(accessToken: string): Promise<Tenant[]> {
|
||||
return tenents;
|
||||
}
|
||||
|
||||
export function useDirectories(): Tenant[] {
|
||||
const { armToken } = useContext(AuthContext);
|
||||
export function useDirectories(armToken: string): Tenant[] {
|
||||
const [state, setState] = useState<Tenant[]>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AuthContext } from "../contexts/authContext";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export async function fetchPhoto(accessToken: string): Promise<Blob | void> {
|
||||
const headers = new Headers();
|
||||
@@ -18,9 +17,8 @@ export async function fetchPhoto(accessToken: string): Promise<Blob | void> {
|
||||
.catch(error => console.log(error));
|
||||
}
|
||||
|
||||
export function useGraphPhoto(): string {
|
||||
export function useGraphPhoto(graphToken: string): string {
|
||||
const [photo, setPhoto] = useState<string>();
|
||||
const { graphToken } = useContext(AuthContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (graphToken) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AuthContext } from "../contexts/authContext";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Subscription } from "../Contracts/DataModels";
|
||||
|
||||
interface SubscriptionListResult {
|
||||
@@ -32,8 +31,7 @@ export async function fetchSubscriptions(accessToken: string): Promise<Subscript
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
export function useSubscriptions(): Subscription[] {
|
||||
const { armToken } = useContext(AuthContext);
|
||||
export function useSubscriptions(armToken: string): Subscription[] {
|
||||
const [state, setState] = useState<Subscription[]>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user