Skip to content

Commit

Permalink
Merge branch 'feature/multiple-accounts' into feature/new-feature-pre…
Browse files Browse the repository at this point in the history
…sentation
  • Loading branch information
ilya-lopukhin committed Mar 14, 2018
2 parents c66fc9d + aa7da03 commit 00259a0
Show file tree
Hide file tree
Showing 15 changed files with 445 additions and 116 deletions.
1 change: 1 addition & 0 deletions app/actions/actionTypes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export const LOGIN_OAUTH_REQUEST = 'auth/LOGIN_OAUTH_REQUEST';
export const LOGOUT_REQUEST = 'auth/LOGOUT_REQUEST';
export const ACCEPT_OAUTH = 'auth/ACCEPT_OAUTH';
export const DENY_OAUTH = 'auth/DENY_OAUTH';
export const SWITCH_ACCOUNT = 'auth/SWITCH_ACCOUNT';
13 changes: 12 additions & 1 deletion app/actions/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ export const loginOAuthRequest = (host: string): AuthAction => ({
host,
});

export const logoutRequest = (): AuthAction => ({
export const logoutRequest = (payload: {
dontForget: boolean
} = { dontForget: false }): AuthAction => ({
type: actionTypes.LOGOUT_REQUEST,
payload,
});

export const acceptOAuth = (code: string): AuthAction => ({
Expand All @@ -32,3 +35,11 @@ export const acceptOAuth = (code: string): AuthAction => ({
export const denyOAuth = (): AuthAction => ({
type: actionTypes.DENY_OAUTH,
});

export const switchAccount = (payload: {|
host: string,
username: string,
|}) => ({
type: actionTypes.SWITCH_ACCOUNT,
payload,
});
49 changes: 46 additions & 3 deletions app/containers/Header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ import type {
import type {
Connector,
} from 'react-redux';
import Tag from '@atlaskit/tag';
import type {
User,
Dispatch,
} from 'types';

import Lozenge from '@atlaskit/lozenge';

import DropdownMenu, {
DropdownItemGroup,
Expand All @@ -43,6 +45,10 @@ import {
refreshWhite,
} from 'data/svg';
import config from 'config';
import ChevronDownIcon from '@atlaskit/icon/glyph/chevron-down';
import EditorAddIcon from '@atlaskit/icon/glyph/editor/add';

import { transformValidHost } from '../../sagas/auth';

import {
HeaderContainer,
Expand All @@ -63,6 +69,10 @@ import {

type Props = {
userData: User,
accounts: Array<{|
host: string,
username: string,
|}>,
host: string,
updateAvailable: string,
updateFetching: boolean,
Expand All @@ -72,6 +82,7 @@ type Props = {

const Header: StatelessFunctionalComponent<Props> = ({
userData,
accounts,
host,
updateAvailable,
updateFetching,
Expand All @@ -88,9 +99,40 @@ const Header: StatelessFunctionalComponent<Props> = ({
<ProfileName>
{userData.displayName}
</ProfileName>
<ProfileTeam>
{host}
</ProfileTeam>
<DropdownMenu
triggerType="default"
position="right top"
trigger={
<ProfileTeam>
{host} <ChevronDownIcon />
</ProfileTeam>
}
>
{accounts.map((ac) => {
const isActive = transformValidHost(ac.host).host === host &&
(ac.username === userData.emailAddress ||
ac.username === userData.key ||
ac.username === userData.name);
return (
<DropdownItem
key={`${ac.host}:${ac.username}`}
onClick={() => dispatch(authActions.switchAccount(ac))}
isDisabled={isActive}
elemAfter={isActive && <Lozenge appearance="success">Active</Lozenge>}
>
<Tag text={ac.host} color="teal" />
{ac.username}
</DropdownItem>
);
})}
<DropdownItem
onClick={() => dispatch(authActions.logoutRequest({ dontForget: true }))}
>
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
<EditorAddIcon /> Add account
</span>
</DropdownItem>
</DropdownMenu>
</ProfileInfo>
</ProfileContainer>

Expand Down Expand Up @@ -168,6 +210,7 @@ function mapStateToProps(state) {
return {
userData: getUserData(state),
host: getUiState('host')(state),
accounts: getUiState('accounts')(state),
updateAvailable: getUiState('updateAvailable')(state),
updateFetching: getUiState('updateFetching')(state),
issuesFetching: getResourceStatus(
Expand Down
7 changes: 5 additions & 2 deletions app/containers/Header/styled/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ export const ProfileName = styled.span`
font-size: 16px;
`;

export const ProfileTeam = styled.span`
// cursor: pointer;
export const ProfileTeam = styled.div`
display: flex;
line-height: 24px;
aling-items: flex-center;
cursor: pointer;
`;

export const DropdownSeparator = styled.div`
Expand Down
2 changes: 2 additions & 0 deletions app/reducers/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
const initialState: UiState = {
initializeInProcess: false,
authorized: false,
accounts: [],
authFormStep: 1,
loginError: null,
loginRequestInProcess: false,
Expand Down Expand Up @@ -59,6 +60,7 @@ const initialState: UiState = {
confirmDeleteWorklog: false,
settings: false,
worklog: false,
accounts: false,
},

flags: [],
Expand Down
112 changes: 97 additions & 15 deletions app/sagas/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import {

import {
setToStorage,
getFromStorage,
removeFromStorage,
} from './storage';
import {
initialConfigureApp,
} from './initializeApp';
import {
throwError,
notify,
} from './ui';
import createIpcChannel from './ipc';

Expand Down Expand Up @@ -99,6 +101,26 @@ export function* chronosBackendAuth({
}
}

function storeInKeytar(payload, host) {
ipcRenderer.sendSync(
'store-credentials',
{
...payload,
host: host.hostname,
},
);
}

function* saveAccount(payload: { host: string, username: string }): Generator<*, void, *> {
const { host } = payload;
let accounts = yield call(getFromStorage, 'accounts');
if (!accounts) accounts = [];
if (!accounts.find(ac => ac.host === host)) {
accounts.push(payload);
yield call(setToStorage, 'accounts', accounts);
}
}

export function* basicAuthLoginForm(): Generator<*, void, *> {
while (true) {
try {
Expand Down Expand Up @@ -141,30 +163,27 @@ export function* basicAuthLoginForm(): Generator<*, void, *> {
*/
yield call(
setToStorage,
'jira_credentials',
'last_used_account',
{
username: payload.username,
host: payload.host,
},
);
yield call(
initialConfigureApp,
saveAccount,
{
host: host.hostname,
protocol,
username: payload.username,
host: payload.host,
},
);
yield call(
(): void => {
ipcRenderer.sendSync(
'store-credentials',
{
...payload,
host: host.hostname,
},
);
initialConfigureApp,
{
host: host.hostname,
protocol,
},
);
yield call(storeInKeytar, payload, host);
yield put(uiActions.setUiState('loginRequestInProcess', false));
trackMixpanel('Jira login');
incrementMixpanel('Jira login', 1);
Expand Down Expand Up @@ -259,7 +278,7 @@ export function* oAuthLoginForm(): Generator<*, *, *> {

export function* logoutFlow(): Generator<*, *, *> {
while (true) {
yield take(actionTypes.LOGOUT_REQUEST);
const { payload: { dontForget } } = yield take(actionTypes.LOGOUT_REQUEST);
try {
const { getGlobal } = remote;
const { running, uploading } = getGlobal('sharedObj');
Expand All @@ -272,9 +291,9 @@ export function* logoutFlow(): Generator<*, *, *> {
// eslint-disable-next-line no-alert
window.alert('Currently app in process of saving worklog, wait few seconds please');
}
if (!running && !uploading) {
if (!running && !uploading && !dontForget) {
yield call(removeFromStorage, 'desktop_tracker_jwt');
yield call(removeFromStorage, 'jira_credentials');
yield call(removeFromStorage, 'last_used_account');
}
yield put({
type: actionTypes.__CLEAR_ALL_REDUCERS__,
Expand Down Expand Up @@ -312,6 +331,69 @@ function getOauthChannelListener(channel, type) {
};
}

export function* switchAccountFlow(): Generator<*, *, *> {
while (true) {
const { payload } = yield take(actionTypes.SWITCH_ACCOUNT);
try {
const { getGlobal } = remote;
const { running, uploading } = getGlobal('sharedObj');

if (running) {
// eslint-disable-next-line no-alert
window.alert('Tracking in progress, save worklog before logout!');
}
if (uploading) {
// eslint-disable-next-line no-alert
window.alert('Currently app in process of saving worklog, wait few seconds please');
}
if (!running && !uploading) {
const host = yield call(transformValidHost, payload.host);
const {
credentials,
error,
} = ipcRenderer.sendSync(
'get-credentials',
{
username: payload.username,
host: host.hostname,
},
);
if (error) {
Raven.captureMessage('keytar error!', {
level: 'error',
extra: {
error: error.err,
},
});
yield call(
throwError,
error.err,
);
if (error.platform === 'linux') {
yield fork(
notify,
{
type: 'libSecretError',
autoDelete: false,
},
);
}
} else {
yield put({
type: actionTypes.__CLEAR_ALL_REDUCERS__,
});
yield put(uiActions.setUiState('initializeInProcess', true));
yield put(authActions.loginRequest({ ...payload, password: credentials.password }));
}
}
trackMixpanel('SwitchAccounts');
incrementMixpanel('SwitchAccounts', 1);
} catch (err) {
yield call(throwError, err);
}
}
}

export function* createIpcAuthListeners(): Generator<*, *, *> {
const oAuthAcceptedChannel = yield call(createIpcChannel, 'oauth-accepted');
const oAuthDeniedChannel = yield call(createIpcChannel, 'oauth-denied');
Expand Down
1 change: 1 addition & 0 deletions app/sagas/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default function* rootSaga(): Generator<*, void, *> {
fork(authSagas.basicAuthLoginForm),
fork(authSagas.oAuthLoginForm),
fork(authSagas.logoutFlow),
fork(authSagas.switchAccountFlow),

// projects
fork(projectSagas.watchFetchProjectStatusesRequest),
Expand Down
7 changes: 6 additions & 1 deletion app/sagas/initializeApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ export function* initialConfigureApp({
yield call(initializeMixpanel);
yield call(identifyInSentryAndMixpanel, host, userData);

let accounts = yield call(getFromStorage, 'accounts');
if (!accounts) accounts = [];
yield put(uiActions.setUiState('accounts', accounts));

const issuesSourceId: Id | null = yield call(getFromStorage, 'issuesSourceId');
const issuesSourceType = yield call(getFromStorage, 'issuesSourceType');
const issuesSprintId: Id | null = yield call(getFromStorage, 'issuesSprintId');
Expand Down Expand Up @@ -153,6 +157,7 @@ export function* initialConfigureApp({
refetchFilterIssuesMarker: false,
},
}));
yield put(uiActions.setUiState('initializeInProcess', false));
/*
const isPaidChronosUser = yield select(getIsPaidUser);
Expand All @@ -166,7 +171,7 @@ export function* initialConfigureApp({
function* getInitializeAppData(): Generator<*, *, *> {
const basicAuthCredentials = yield call(
getFromStorage,
'jira_credentials',
'last_used_account',
);
const basicAuthDataExist =
basicAuthCredentials !== null &&
Expand Down
1 change: 0 additions & 1 deletion app/styles/index.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @flow
import styled from 'styled-components2';


export const AppWrapper = styled.div`
height: 100%;
overflow: hidden;
Expand Down
3 changes: 3 additions & 0 deletions app/types/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export type AuthAction =
|} |
{|
type: typeof actionTypes.LOGOUT_REQUEST,
payload: {
dontForget: boolean,
}
|} |
{|
type: typeof actionTypes.ACCEPT_OAUTH,
Expand Down
1 change: 1 addition & 0 deletions app/types/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type User = {
items: Array<any>,
},
key: string,
name: string,
locale?: string,
self: string,
timeZone: string,
Expand Down
Loading

0 comments on commit 00259a0

Please sign in to comment.