Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: sovereign clouds #955

Open
wants to merge 61 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
e46710a
get graph version from content between slashes
thewahome May 3, 2021
2f94af6
detect cloud from application locale
thewahome May 3, 2021
240e618
create type, action-creator and reducer
thewahome May 3, 2021
2ca7d7c
change base-url on page load for pre-selected cloud
thewahome May 4, 2021
787e42d
change base-url for app when config is set
thewahome May 4, 2021
5b7d256
change parse function to handle long versions
thewahome May 6, 2021
9f76404
destructure sample urls to parts regardless of version size
thewahome May 6, 2021
ed412ae
update base links with current cloud on reload and selection
thewahome May 6, 2021
050180e
replace references to GRAPH_URL constant
thewahome May 6, 2021
263eafa
sanitise urls with cloud base urls
thewahome May 6, 2021
92ff83c
remove German cloud support
thewahome May 10, 2021
0d3cb55
change login url with cloud change
thewahome May 10, 2021
39a5554
display UI to manually select sovereign cloud
thewahome May 11, 2021
f36ca72
reset to global cloud when logging out
thewahome May 11, 2021
3d559b2
display link only when options are available
thewahome May 12, 2021
7652835
move sovereign cloud logic to own component
thewahome May 12, 2021
d3c1c5a
reuse dialog in cloud selection pop up
thewahome May 12, 2021
995167b
move module logic to modules folder
thewahome May 19, 2021
9d37e6d
set default cloud in state when none selected
thewahome May 19, 2021
715a9b0
extract theme chooser from settings file
thewahome May 19, 2021
03d79d8
extract permissions from settings file
thewahome May 20, 2021
ba2477a
add china cloud client ID to configuration
thewahome May 24, 2021
cb7ce35
Merge branch 'dev' into feature/sovereign-clouds
thewahome May 24, 2021
ff595da
place appropriate login url
thewahome May 25, 2021
0fe995b
clear log in details when cloud is switched
thewahome May 25, 2021
ce77191
display cloud chooser prompt only once
thewahome May 25, 2021
bd295a2
sign out locally
thewahome May 26, 2021
2270cee
place message string in translation file
thewahome May 26, 2021
40cecfc
default to null early
thewahome May 26, 2021
53ba1a9
rename filter variables
thewahome May 26, 2021
a795cdc
sign out explicitly
thewahome May 27, 2021
41e4476
Merge branch 'dev' into feature/sovereign-clouds
thewahome Jun 17, 2021
e650e61
Merge branch 'dev' into feature/sovereign-clouds
thewahome Jun 18, 2021
6b2bb3c
fix call to get logged in user's profile
thewahome Jul 9, 2021
3f89732
replace the base url to match current cloud
thewahome Jul 9, 2021
ac6cdf7
logout from account in current cloud
thewahome Jul 9, 2021
d97e03c
reloads page when cloud is changed and profile exists
thewahome Jul 9, 2021
2626bfa
change profile source only when it doesnt match with global
thewahome Jul 9, 2021
85a32b9
Merge branch 'dev' into feature/sovereign-clouds
thewahome Jul 9, 2021
13f13d3
Merge branch 'dev' into feature/sovereign-clouds
thewahome Aug 30, 2021
7255ff1
change package reference to @fluentui/react
thewahome Aug 30, 2021
dfb5cd0
Merge branch 'dev' into feature/sovereign-clouds
thewahome Nov 19, 2021
3e8d728
fix linting errors
thewahome Nov 19, 2021
b2082c1
set matching file
thewahome Nov 19, 2021
7c35994
disable china cloud support
thewahome Nov 19, 2021
6527374
Merge branch 'dev' into feature/sovereign-clouds
thewahome Nov 24, 2021
617cfac
reset to global cloud when user logs out
thewahome Nov 24, 2021
9ee5240
Merge branch 'feature/sovereign-clouds' of https://github.com/microso…
thewahome Nov 24, 2021
c95c205
Merge branch 'dev' into feature/sovereign-clouds
thewahome Dec 3, 2021
547b296
fix linting errors
thewahome Dec 6, 2021
b04ab30
remove code smells
thewahome Dec 6, 2021
9938b35
Merge branch 'dev' into feature/sovereign-clouds
thewahome Dec 6, 2021
c985ea2
Merge branch 'dev' into feature/sovereign-clouds
thewahome May 20, 2022
c7e307f
Merge branch 'dev' into feature/sovereign-clouds
thewahome May 20, 2022
339d04e
Merge branch 'dev' into feature/sovereign-clouds
thewahome Jul 22, 2022
f51c8bf
Merge branch 'dev' into feature/sovereign-clouds
thewahome Jul 29, 2022
c02cd64
fix cloud telemetry
thewahome Jul 29, 2022
ba76b4b
enable canary only for test tenants
thewahome Jul 29, 2022
2a4ed3e
Merge branch 'dev' into feature/sovereign-clouds
Onokaev Aug 4, 2022
c9a87ae
Merge branch 'dev' into feature/sovereign-clouds
thewahome Feb 15, 2024
66dc7fe
[temp] use en-us to trigger cloud choice
thewahome Feb 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- name: Build And Deploy
env:
REACT_APP_CLIENT_ID: ${{ secrets.REACT_APP_CLIENT_ID }}
REACT_APP_CLIENT_ID_CHINA: ${{ secrets.REACT_APP_CLIENT_ID_CHINA }}
REACT_APP_FEEDBACK_CAMPAIGN_ID: ${{ secrets.REACT_APP_FEEDBACK_CAMPAIGN_ID }},
REACT_APP_NPS_FEEDBACK_CAMPAIGN_ID: ${{secrets.REACT_APP_NPS_FEEDBACK_CAMPAIGN_ID}}
REACT_APP_INSTRUMENTATION_KEY: ${{ secrets.REACT_APP_STAGING_INSTRUMENTATION_KEY }}
Expand Down
9 changes: 9 additions & 0 deletions src/app/services/actions/cloud-action-creator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IAction } from '../../../types/action';
import { SET_ACTIVE_CLOUD_SUCCESS } from '../redux-constants';

export function setActiveCloud(response: object): IAction {
Copy link
Contributor

@Onokaev Onokaev Dec 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Can we add a test for this action?

return {
type: SET_ACTIVE_CLOUD_SUCCESS,
response
};
}
3 changes: 1 addition & 2 deletions src/app/services/actions/snippet-action-creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ export function getSnippet(language: string): Function {
'Content-Type': 'application/http'
};
// eslint-disable-next-line max-len
const body = `${sampleQuery.selectedVerb} /${queryVersion}/${
requestUrl + search
const body = `${sampleQuery.selectedVerb} /${queryVersion}/${requestUrl + search
} HTTP/1.1\r\nHost: graph.microsoft.com\r\nContent-Type: application/json\r\n\r\n${JSON.stringify(
sampleQuery.sampleBody
)}`;
Expand Down
5 changes: 5 additions & 0 deletions src/app/services/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { usePrevious } from './usePrevious';

export default {
usePrevious
}
17 changes: 17 additions & 0 deletions src/app/services/hooks/usePrevious.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useRef, useEffect } from 'react';
/**
* Keeps track of the previous value of the item passed to it before a re-render
*
* @param value
* @description The ref object is a generic container whose current property is mutable
* and can hold any value, similar to an instance property on a class
* @returns Return previous value
* (happens before update in useEffect)
*/
export function usePrevious(value: any) {
const ref = useRef('');
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
13 changes: 13 additions & 0 deletions src/app/services/reducers/cloud-reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { globalCloud } from '../../../modules/sovereign-clouds';
import { IAction } from '../../../types/action';
import { ICloud } from '../../../types/cloud';
import { SET_ACTIVE_CLOUD_SUCCESS } from '../redux-constants';

const initialState: ICloud = globalCloud;

export function cloud(state = initialState, action: IAction): ICloud {
Copy link
Contributor

@Onokaev Onokaev Dec 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Can we add a test for this reducer?

if (action.type === SET_ACTIVE_CLOUD_SUCCESS) {
return action.response;
}
return state;
}
2 changes: 2 additions & 0 deletions src/app/services/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { combineReducers } from 'redux';
import { adaptiveCard } from './adaptive-cards-reducer';
import { authToken, consentedScopes } from './auth-reducers';
import { autoComplete } from './autocomplete-reducer';
import { cloud } from './cloud-reducer';
import { devxApi } from './devxApi-reducers';
import { dimensions } from './dimensions-reducers';
import { graphExplorerMode } from './graph-explorer-mode-reducer';
Expand All @@ -28,6 +29,7 @@ export default combineReducers({
adaptiveCard,
authToken,
autoComplete,
cloud,
consentedScopes,
devxApi,
dimensions,
Expand Down
1 change: 1 addition & 0 deletions src/app/services/redux-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const AUTOCOMPLETE_FETCH_PENDING = 'AUTOCOMPLETE_FETCH_PENDING';
export const RESIZE_SUCCESS = 'RESIZE_SUCCESS';
export const RESPONSE_EXPANDED = 'RESPONSE_EXPANDED';
export const PERMISSIONS_PANEL_OPEN = 'PERMISSIONS_PANEL_OPEN';
export const SET_ACTIVE_CLOUD_SUCCESS = 'SET_ACTIVE_CLOUD_SUCCESS';
export const AUTHENTICATION_PENDING = 'AUTHENTICATION_PENDING';
export const SET_GRAPH_PROXY_URL = 'SET_GRAPH_PROXY_URL';
export const FETCH_RESOURCES_SUCCESS = 'RESOURCES_FETCH_SUCCESS';
Expand Down
4 changes: 2 additions & 2 deletions src/app/utils/query-url-sanitization.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable no-useless-escape */
import { IQuery } from '../../types/query-runner';
import { GRAPH_URL } from '../services/graph-constants';
import {
isAllAlpha,
sanitizeQueryParameter
Expand Down Expand Up @@ -56,6 +55,7 @@ export function sanitizeGraphAPISandboxUrl(url: string): string {
*/
export function sanitizeQueryUrl(url: string): string {
url = decodeURIComponent(url);
const { origin } = new URL(url);

const { search, queryVersion, requestUrl } = parseSampleUrl(url);
const queryString: string = search
Expand Down Expand Up @@ -83,7 +83,7 @@ export function sanitizeQueryUrl(url: string): string {
});
}

return `${GRAPH_URL}/${queryVersion}/${resourceUrl}${queryString}`;
return `${origin}/${queryVersion}/${resourceUrl}${queryString}`;
}

/**
Expand Down
43 changes: 32 additions & 11 deletions src/app/utils/sample-url-generation.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { GRAPH_URL } from '../services/graph-constants';
interface IParsedSample {
queryVersion: string;
requestUrl: string;
sampleUrl: string;
search: string;
}

export function parseSampleUrl(url: string, version?: string) {
export function parseSampleUrl(url: string, version?: string): IParsedSample {
let requestUrl = '';
let queryVersion = '';
let sampleUrl = '';
let search = '';

if (url !== '') {
try {
const urlObject: URL = new URL(url);
requestUrl = decodeURIComponent(urlObject.pathname.substr(6).replace(/\/$/, ''));
queryVersion = (version) ? version : urlObject.pathname.substring(1, 5);
search = generateSearchParameters(urlObject, search);
sampleUrl = `${GRAPH_URL}/${queryVersion}/${requestUrl + search}`;
} catch (error) {
queryVersion = (version) ? version : getGraphVersion(url);
requestUrl = getRequestUrl(url, queryVersion);
search = generateSearchParameters(url, search);
sampleUrl = generateSampleUrl(url, queryVersion, requestUrl, search);
} catch (error: any) {
if (error.message === 'Failed to construct \'URL\': Invalid URL') {
return {
queryVersion, requestUrl, sampleUrl, search
Expand All @@ -27,13 +31,26 @@ export function parseSampleUrl(url: string, version?: string) {
};
}

function generateSearchParameters(urlObject: URL, search: string) {
const searchParameters = urlObject.search;
function getRequestUrl(url: string, version: string): string {
const { pathname } = new URL(url);
const versionToReplace = (pathname.startsWith(`/${version}`)) ? version : getGraphVersion(url);
const requestContent = pathname.split(versionToReplace + '/').pop()!;
return decodeURIComponent(requestContent?.replace(/\/$/, ''));
}

function getGraphVersion(url: string): string {
const { pathname } = new URL(url);
const parts = pathname.substring(1).split('/');
return parts[0];
}

function generateSearchParameters(url: string, search: string) {
const { search: searchParameters } = new URL(url);
if (searchParameters) {
try {
search = decodeURI(searchParameters);
}
catch (error) {
catch (error: any) {
if (error.message === 'URI malformed') {
search = searchParameters;
}
Expand All @@ -42,3 +59,7 @@ function generateSearchParameters(urlObject: URL, search: string) {
return search;
}

function generateSampleUrl(url: string, queryVersion: string, requestUrl: string, search: string): string {
const { origin } = new URL(url);
return `${origin}/${queryVersion}/${requestUrl + search}`;
}
2 changes: 1 addition & 1 deletion src/app/utils/translate-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function translateMessage(messageId: string): string {
function getTranslation(messageId: string, locale: string) {
const localeMessages: object = (messages as { [key: string]: object })[locale];
if (localeMessages) {
const key: any = Object.keys(localeMessages).find(k => k === messageId);
const key: any = Object.keys(localeMessages).find(id => id === messageId);
const message = (localeMessages as { [key: string]: object })[key];
if (message) {
return message.toString();
Expand Down
27 changes: 27 additions & 0 deletions src/app/views/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { bindActionCreators, Dispatch } from 'redux';

import { geLocale } from '../../appLocale';
import { authenticationWrapper } from '../../modules/authentication';
import { getCurrentCloud, getEligibleCloud } from '../../modules/sovereign-clouds';
import { componentNames, eventTypes, telemetry } from '../../telemetry';
import { loadGETheme } from '../../themes';
import { ThemeContext } from '../../themes/theme-context';
Expand Down Expand Up @@ -40,6 +41,7 @@ import { QueryResponse } from './query-response';
import { QueryRunner } from './query-runner';
import { parse } from './query-runner/util/iframe-message-parser';
import { Settings } from './settings';
import { SovereignClouds } from './settings/SovereignClouds';
import { Sidebar } from './sidebar/Sidebar';

interface IAppProps {
Expand Down Expand Up @@ -69,6 +71,7 @@ interface IAppState {
selectedVerb: string;
mobileScreen: boolean;
hideDialog: boolean;
showCloudDialog: boolean;
}

class App extends Component<IAppProps, IAppState> {
Expand All @@ -82,6 +85,7 @@ class App extends Component<IAppProps, IAppState> {
this.state = {
selectedVerb: 'GET',
mobileScreen: false,
showCloudDialog: false,
hideDialog: true
};
}
Expand Down Expand Up @@ -122,8 +126,25 @@ class App extends Component<IAppProps, IAppState> {
// Listens for messages from host document
window.addEventListener('message', this.receiveMessage, false);
this.handleSharedQueries();
this.toggleConfirmCloud();
};

public toggleConfirmCloud = () => {
if (this.state.showCloudDialog) {
this.setState({
showCloudDialog: false
})
} else {
const currentCloud = getCurrentCloud();
const eligibleCloud = getEligibleCloud();
if (!currentCloud && eligibleCloud) {
this.setState({
showCloudDialog: true
})
}
}
}

public handleSharedQueries() {
const { actions } = this.props;
const queryStringParams = this.getQueryStringParams();
Expand Down Expand Up @@ -310,6 +331,7 @@ class App extends Component<IAppProps, IAppState> {
};

public render() {
const { showCloudDialog } = this.state;
const classes = classNames(this.props);
const { authenticated, graphExplorerMode, queryState, minimised, termsOfUse, sampleQuery,
actions, sidebarProperties }: any = this.props;
Expand Down Expand Up @@ -403,6 +425,11 @@ class App extends Component<IAppProps, IAppState> {
</div>
</div>
</div>
<SovereignClouds
prompt={true}
cloudSelectorOpen={showCloudDialog}
toggleCloudSelector={this.toggleConfirmCloud}
/>
</ThemeContext.Provider>
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/app/views/app-sections/StatusMessages.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Link, MessageBar } from '@fluentui/react';
import React, { Fragment } from 'react';
import { FormattedMessage } from 'react-intl';
import { replaceBaseUrl } from '../../../modules/sovereign-clouds';

import { IQuery } from '../../../types/query-runner';
import { GRAPH_URL } from '../../services/graph-constants';
Expand Down Expand Up @@ -38,7 +39,7 @@ export function statusMessages(queryState: any, sampleQuery: IQuery, actions: an

function setQuery(link: string) {
const query: IQuery = { ...sampleQuery };
query.sampleUrl = link;
query.sampleUrl = replaceBaseUrl(link);
actions.setSampleQuery(query);
}

Expand Down
11 changes: 5 additions & 6 deletions src/app/views/authentication/Authentication.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ const Authentication = (props: any) => {
dispatch(getConsentedScopesSuccess(authResponse.scopes));
}
} catch (error: any) {
const { errorCode } = error;
if (signInAuthError(errorCode)) {
const errorCode = error.errorCode;
if (errorCode && signInAuthError(errorCode)) {
authenticationWrapper.clearSession();
}
dispatch(
setQueryResponseStatus({
ok: false,
statusText: messages['Authentication failed'],
status: removeUnderScore(errorCode),
status: (errorCode) ? removeUnderScore(errorCode) : '',
messageType: MessageBarType.error,
hint: getSignInAuthErrorHint(errorCode)
hint: (errorCode) ? getSignInAuthErrorHint(errorCode) : null
})
);
setLoginInProgress(false);
Expand All @@ -61,8 +61,7 @@ const Authentication = (props: any) => {
SeverityLevel.Error,
{
ComponentName: componentNames.AUTHENTICATION_ACTION,
Message: `Authentication failed: ${errorCode ? removeUnderScore(errorCode) : ''
}`
Message: `Authentication failed: ${errorCode ? removeUnderScore(errorCode) : ''}`
}
);
}
Expand Down
6 changes: 5 additions & 1 deletion src/app/views/authentication/AuthenticationErrorsHints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export function getSignInAuthErrorHint(error: string): string {
}

export function signInAuthError(error: string): boolean {
return authErrorList.includes(error.trim());
console.log({ error });
Onokaev marked this conversation as resolved.
Show resolved Hide resolved
if (error) {
return authErrorList.includes(error.trim());
}
return false;
}

export function getConsentAuthErrorHint(error: string): string {
Expand Down
Loading