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

Fix: interaction in progress #976

Merged
merged 7 commits into from
Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
13 changes: 11 additions & 2 deletions src/app/services/actions/auth-action-creators.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { authenticationWrapper } from '../../../modules/authentication';
import { IAction } from '../../../types/action';
import { Mode } from '../../../types/enums';
import { GET_AUTH_TOKEN_SUCCESS, GET_CONSENTED_SCOPES_SUCCESS, LOGOUT_SUCCESS } from '../redux-constants';
import { AUTHENTICATION_PENDING, GET_AUTH_TOKEN_SUCCESS, GET_CONSENTED_SCOPES_SUCCESS, LOGOUT_SUCCESS } from '../redux-constants';

export function getAuthTokenSuccess(response: boolean): any {
return {
type: GET_AUTH_TOKEN_SUCCESS,
response,
};
}

export function getConsentedScopesSuccess(response: string[]): IAction {
return {
type: GET_CONSENTED_SCOPES_SUCCESS,
Expand All @@ -23,15 +24,23 @@ export function signOutSuccess(response: boolean): any {
};
}

export function setAuthenticationPending(response: boolean): any {
return {
type: AUTHENTICATION_PENDING,
response,
};
}

export function signOut() {
return (dispatch: Function, getState: Function) => {
const { graphExplorerMode } = getState();
dispatch(setAuthenticationPending(true));
if (graphExplorerMode === Mode.Complete) {
authenticationWrapper.logOut();
} else {
authenticationWrapper.logOutPopUp();
dispatch(signOutSuccess(false));
}
dispatch(signOutSuccess(false));
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/services/actions/query-action-creators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { addHistoryItem } from './request-history-action-creators';

export function runQuery(query: IQuery): Function {
return (dispatch: Function, getState: Function) => {
const tokenPresent = getState().authToken;
const tokenPresent = !!getState()?.authToken?.token;
const respHeaders: any = {};
const createdAt = new Date().toISOString();

Expand Down
29 changes: 25 additions & 4 deletions src/app/services/reducers/auth-reducers.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
import { IAction } from '../../../types/action';
import { GET_AUTH_TOKEN_SUCCESS, GET_CONSENTED_SCOPES_SUCCESS, LOGOUT_SUCCESS } from '../redux-constants';
import { IAuthenticate } from '../../../types/authentication';
import {
AUTHENTICATION_PENDING, GET_AUTH_TOKEN_SUCCESS,
GET_CONSENTED_SCOPES_SUCCESS, LOGOUT_SUCCESS
} from '../redux-constants';

export function authToken(state = {}, action: IAction): string | object {

const initialState: IAuthenticate = {
pending: false,
token: false
}

export function authToken(state = initialState, action: IAction): IAuthenticate {
switch (action.type) {
case GET_AUTH_TOKEN_SUCCESS:
return action.response;
return {
token: true,
pending: false
};
case LOGOUT_SUCCESS:
return action.response;
return {
token: false,
pending: false
};
case AUTHENTICATION_PENDING:
return {
token: true,
pending: true
};
default:
return state;
}
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 @@ -41,3 +41,4 @@ 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 AUTHENTICATION_PENDING = 'AUTHENTICATION_PENDING';
2 changes: 1 addition & 1 deletion src/app/views/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ const mapStateToProps = ({ sidebarProperties, theme,
termsOfUse,
minimised: !mobileScreen && !showSidebar,
sampleQuery,
authenticated: !!authToken
authenticated: !!authToken.token
};
};

Expand Down
145 changes: 60 additions & 85 deletions src/app/views/authentication/Authentication.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,57 @@

import { SeverityLevel } from '@microsoft/applicationinsights-web';
import { Icon, Label, MessageBarType, Spinner, SpinnerSize, styled } from 'office-ui-fabric-react';
import React, { Component } from 'react';
import React, { useState } from 'react';
import { FormattedMessage, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { useDispatch, useSelector } from 'react-redux';
import { authenticationWrapper } from '../../../modules/authentication';

import { componentNames, errorTypes, telemetry } from '../../../telemetry';
import { IAuthenticationProps } from '../../../types/authentication';
import { Mode } from '../../../types/enums';
import { IRootState } from '../../../types/root';
import * as authActionCreators from '../../services/actions/auth-action-creators';
import * as queryStatusActionCreators from '../../services/actions/query-status-action-creator';
import { getAuthTokenSuccess, getConsentedScopesSuccess } from '../../services/actions/auth-action-creators';
import { setQueryResponseStatus } from '../../services/actions/query-status-action-creator';
import { translateMessage } from '../../utils/translate-messages';
import { classNames } from '../classnames';
import { showSignInButtonOrProfile } from './auth-util-components';
import { authenticationStyles } from './Authentication.styles';

export class Authentication extends Component<IAuthenticationProps, { loginInProgress: boolean }> {
constructor(props: IAuthenticationProps) {
super(props);
this.state = { loginInProgress: false };
}
const Authentication = (props: any) => {
const dispatch = useDispatch();
const [loginInProgress, setLoginInProgress] = useState(false);
const { sidebarProperties, authToken, graphExplorerMode } = useSelector((state: IRootState) => state);
const mobileScreen = !!sidebarProperties.mobileScreen;
const showSidebar = !!sidebarProperties.showSidebar;
const tokenPresent = !!authToken.token;
const logoutInProgress = !!authToken.pending;
const minimised = !mobileScreen && !showSidebar;

public signIn = async (): Promise<void> => {
const {
intl: { messages },
}: any = this.props;
this.setState({ loginInProgress: true });
const classes = classNames(props);

const {
intl: { messages },
}: any = props;
const signIn = async (): Promise<void> => {
setLoginInProgress(true);

try {
const authResponse = await authenticationWrapper.logIn();
if (authResponse) {
this.setState({ loginInProgress: false });

this.props.actions!.signIn(authResponse.accessToken);
this.props.actions!.storeScopes(authResponse.scopes);
setLoginInProgress(false);
dispatch(getAuthTokenSuccess(!!authResponse.accessToken))
dispatch(getConsentedScopesSuccess(authResponse.scopes))
}
} catch (error) {
const { errorCode } = error;
this.props.actions!.setQueryResponseStatus({
dispatch(setQueryResponseStatus({
ok: false,
statusText: messages['Authentication failed'],
status: errorCode === 'popup_window_error'
? translateMessage('popup blocked, allow pop-up windows in your browser')
: errorCode ? errorCode.replace('_', ' ') : '',
messageType: MessageBarType.error
});
this.setState({ loginInProgress: false });
}));
setLoginInProgress(false);
telemetry.trackException(
new Error(errorTypes.OPERATIONAL_ERROR),
SeverityLevel.Error,
Expand All @@ -57,78 +60,50 @@ export class Authentication extends Component<IAuthenticationProps, { loginInPro
Message: `Authentication failed: ${errorCode ? errorCode.replace('_', ' ') : ''}`,
});
}

};

public render() {
const { minimised, tokenPresent, mobileScreen, graphExplorerMode } = this.props;
const classes = classNames(this.props);
const { loginInProgress } = this.state;

return (
<>
{loginInProgress ? showLoginInProgressSpinner(classes, minimised)
:
mobileScreen ? showSignInButtonOrProfile(tokenPresent, mobileScreen, this.signIn, minimised) :
<>
{!tokenPresent && graphExplorerMode === Mode.Complete && !minimised && showUnAuthenticatedText(classes)}
<br />{showSignInButtonOrProfile(tokenPresent, mobileScreen, this.signIn, minimised)}<br />
</>}
</>
);
const showProgressSpinner = (): React.ReactNode => {
return <div className={classes.spinnerContainer}>
<Spinner className={classes.spinner} size={SpinnerSize.medium} />
{!minimised && <Label>
<FormattedMessage id={`Signing you ${loginInProgress ? 'in' : 'out'}...`} />
</Label>}
</div>;
}
}

function showUnAuthenticatedText(classes: any): React.ReactNode {
return <>
<Label className={classes.authenticationLabel}>
<Icon iconName='Permissions' className={classes.keyIcon} />
<FormattedMessage id='Authentication' />
</Label>

<br />
<Label>
<FormattedMessage id='Using demo tenant' /> <FormattedMessage id='To access your own data:' />
</Label>
</>;
}
const showUnAuthenticatedText = (): React.ReactNode => {
return <>
<Label className={classes.authenticationLabel}>
<Icon iconName='Permissions' className={classes.keyIcon} />
<FormattedMessage id='Authentication' />
</Label>

function showLoginInProgressSpinner(classes: any, minimised: boolean): React.ReactNode {
return <div className={classes.spinnerContainer}>
<Spinner className={classes.spinner} size={SpinnerSize.medium} />
{!minimised && <Label>
<FormattedMessage id='Signing you in...' />
</Label>}
</div>;
}
<br />
<Label>
<FormattedMessage id='Using demo tenant' /> <FormattedMessage id='To access your own data:' />
</Label>
</>;
}

function mapStateToProps({ sidebarProperties, theme, authToken, graphExplorerMode }: IRootState) {
const mobileScreen = !!sidebarProperties.mobileScreen;
const showSidebar = !!sidebarProperties.showSidebar;
return {
tokenPresent: !!authToken,
mobileScreen,
appTheme: theme,
minimised: !mobileScreen && !showSidebar,
graphExplorerMode
};
}
if (logoutInProgress) {
return showProgressSpinner();
}

function mapDispatchToProps(dispatch: Dispatch): object {
return {
actions: bindActionCreators({
...authActionCreators,
...queryStatusActionCreators
},
dispatch)
};
return (
<>
{loginInProgress ? showProgressSpinner()
:
mobileScreen ? showSignInButtonOrProfile(tokenPresent, mobileScreen, signIn, minimised) :
<>
{!tokenPresent && graphExplorerMode === Mode.Complete && !minimised && showUnAuthenticatedText()}
<br />{showSignInButtonOrProfile(tokenPresent, mobileScreen, signIn, minimised)}<br />
</>}
</>
)
}

// @ts-ignore
const IntlAuthentication = injectIntl(Authentication);
// @ts-ignore
const StyledAuthentication = styled(IntlAuthentication, authenticationStyles);
export default connect(
mapStateToProps,
mapDispatchToProps
)(StyledAuthentication);
export default StyledAuthentication;
2 changes: 1 addition & 1 deletion src/app/views/query-runner/query-input/QueryInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const QueryInput = (props: IQueryInputProps) => {

const { sampleQuery, authToken,
isLoadingData: submitting } = useSelector((state: IRootState) => state);
const authenticated = !!authToken;
const authenticated = !!authToken.token;

const showError = !authenticated && sampleQuery.selectedVerb !== 'GET';
const verbSelector: any = queryRunnerStyles().verbSelector;
Expand Down
2 changes: 1 addition & 1 deletion src/app/views/query-runner/request/auth/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function Auth(props: any) {
iconName: 'code'
};

if (!authToken) {
if (!authToken.token) {
return <Label className={classes.emptyStateLabel}>
<FormattedMessage id='Sign In to see your access token.' />
</Label>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const PanelList = ({ messages,
const { consentedScopes, scopes, authToken } = useSelector((state: IRootState) => state);
const [permissions, setPermissions] = useState(scopes.data.sort(dynamicSort('value', SortOrder.ASC)));
const permissionsList: any[] = [];
const tokenPresent = !!authToken;
const tokenPresent = !!authToken.token;

setConsentedStatus(tokenPresent, permissions, consentedScopes);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ function mapStateToProps({ sampleQuery, scopes, authToken, consentedScopes, dime
return {
sample: sampleQuery,
scopes,
tokenPresent: authToken,
tokenPresent: authToken.token,
consentedScopes,
dimensions
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const TabList = ({ columns, classes, renderItemColumn, renderDetailsHeader, maxH
const dispatch = useDispatch();
const { consentedScopes, scopes, authToken } = useSelector((state: IRootState) => state);
const permissions: IPermission[] = scopes.hasUrl ? scopes.data : [];
const tokenPresent = !!authToken;
const tokenPresent = !!authToken.token;

setConsentedStatus(tokenPresent, permissions, consentedScopes);

Expand Down
8 changes: 3 additions & 5 deletions src/app/views/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import React, { useEffect, useState } from 'react';
import { FormattedMessage, injectIntl } from 'react-intl';
import { useDispatch, useSelector } from 'react-redux';

import '../../utils/string-operations';
import '../../utils/string-operations';
import { geLocale } from '../../../appLocale';
import { componentNames, eventTypes, telemetry } from '../../../telemetry';
import { loadGETheme } from '../../../themes';
Expand All @@ -33,7 +33,8 @@ import { Permission } from '../query-runner/request/permissions';

function Settings(props: ISettingsProps) {
const dispatch = useDispatch();
const { permissionsPanelOpen } = useSelector((state: IRootState) => state);
const { permissionsPanelOpen, authToken, theme: appTheme } = useSelector((state: IRootState) => state);
const authenticated = authToken.token;
const [themeChooserDialogHidden, hideThemeChooserDialog] = useState(true);
const [items, setItems] = useState([]);
const [selectedPermissions, setSelectedPermissions] = useState([]);
Expand All @@ -42,9 +43,6 @@ function Settings(props: ISettingsProps) {
intl: { messages }
}: any = props;

const authenticated = useSelector((state: any) => (!!state.authToken));
const appTheme = useSelector((state: any) => (state.theme));

useEffect(() => {
const menuItems: any = [
{
Expand Down
2 changes: 1 addition & 1 deletion src/app/views/sidebar/sample-queries/SampleQueries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ function displayTipMessage(actions: any, selectedQuery: ISampleQuery) {

function mapStateToProps({ authToken, profile, samples, theme }: IRootState) {
return {
tokenPresent: !!authToken,
tokenPresent: !!authToken.token,
profile,
samples,
appTheme: theme
Expand Down
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const currentTheme = readTheme();
loadGETheme(currentTheme);

const appState: any = store({
authToken: '',
authToken: { token: false, pending: false },
consentedScopes: [],
isLoadingData: false,
profile: null,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/GE.json
Original file line number Diff line number Diff line change
Expand Up @@ -351,5 +351,6 @@
"Click here to go to the next page": "Click here to go to the next page",
"and experiment on": " and experiment on",
"Scope consent failed": "Scope consent failed",
"Missing url": "Please enter a valid url to run the query"
"Missing url": "Please enter a valid url to run the query",
"Signing you out...": "Signing you out..."
}
Loading