Skip to content

Commit

Permalink
feat: Update APU feature to support onboarding and updating AppStream
Browse files Browse the repository at this point in the history
accounts  (awslabs#606)
  • Loading branch information
Tim Nguyen committed Jul 30, 2021
1 parent 41a9936 commit 47872f2
Show file tree
Hide file tree
Showing 22 changed files with 424 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ const AwsAccount = types
updatedBy: '',
budget: types.optional(Budget, {}),
stackInfo: types.optional(AwsStackInfo, {}),
isAppStreamConfigured: false,
appStreamStackName: types.maybe(types.string),
appStreamFleetName: types.maybe(types.string),
appStreamSecurityGroupId: types.maybe(types.string),
})
.actions(self => ({
setAwsAccounts(rawAwsAccounts) {
Expand All @@ -108,6 +112,14 @@ const AwsAccount = types
self.updatedAt = rawAwsAccounts.updatedAt || self.updatedAt;
self.createdBy = rawAwsAccounts.createdBy || self.createdBy;
self.updatedBy = rawAwsAccounts.updatedBy || self.updatedBy;
self.appStreamStackName = rawAwsAccounts.appStreamStackName;
self.appStreamFleetName = rawAwsAccounts.appStreamFleetName;
self.appStreamSecurityGroupId = rawAwsAccounts.appStreamSecurityGroupId;
self.isAppStreamConfigured =
!_.isUndefined(rawAwsAccounts.appStreamStackName) &&
!_.isUndefined(rawAwsAccounts.appStreamFleetName) &&
!_.isUndefined(rawAwsAccounts.appStreamSecurityGroupId);
self.rev = rawAwsAccounts.rev || 0;

// Can't use || for needsPermissionUpdate because the value is a Boolean
// we don't update the other fields because they are being populated by a separate store
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ const AwsAccountsStore = BaseStore.named('AwsAccountsStore')
res.cfnStackName = awsAccount.cfnStackName;
res.cfnStackId = awsAccount.cfnStackId;
res.updatedAt = awsAccount.updatedAt;
res.appStreamStackName = awsAccount.appStreamStackName;
res.appStreamFleetName = awsAccount.appStreamFleetName;
res.appStreamSecurityGroupId = awsAccount.appStreamSecurityGroupId;
res.isAppStreamConfigured =
!_.isUndefined(awsAccount.appStreamStackName) &&
!_.isUndefined(awsAccount.appStreamFleetName) &&
!_.isUndefined(awsAccount.appStreamSecurityGroupId);
res.rev = awsAccount.rev;
result.push(res);
});
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('AwsAccountsStore', () => {
});

describe('addAwsAccount', () => {
it('should add a new Aws Account successfully', async () => {
it('should successfully add a new Aws Account without AppStream configured', async () => {
// BUILD
getAwsAccounts.mockResolvedValue([]);
addAwsAccount.mockResolvedValue(newAwsAccount);
Expand All @@ -57,10 +57,41 @@ describe('AwsAccountsStore', () => {
// OPERATE
await store.addAwsAccount(newAwsAccount);

const expectedAwsAccount = {
...newAwsAccount,
isAppStreamConfigured: false,
appStreamFleetName: undefined,
appStreamSecurityGroupId: undefined,
appStreamStackName: undefined,
};
delete expectedAwsAccount.createdAt;
// CHECK
expect(newAwsAccount).toMatchObject(store.list[0]);
// some properties are dropped when added, so this makes sure store.list[0]
// is a subset of newAwsAccount
expect(store.list[0]).toMatchObject(expectedAwsAccount);
});

it('should successfully add a new Aws Account with AppStream configured', async () => {
// BUILD
getAwsAccounts.mockResolvedValue([]);
const appStreamFleetName = 'fleet1';
const appStreamSecurityGroupId = 'sg1';
const appStreamStackName = 'stack1';
const appStreamConfiguredAwsAccount = {
...newAwsAccount,
appStreamFleetName,
appStreamSecurityGroupId,
appStreamStackName,
};
addAwsAccount.mockResolvedValue(appStreamConfiguredAwsAccount);
getAllAccountsPermissionStatus.mockResolvedValue(permRetVal);
await store.load();

// OPERATE
await store.addAwsAccount(appStreamConfiguredAwsAccount);

// CHECK
const expectedAwsAccount = { ...appStreamConfiguredAwsAccount, isAppStreamConfigured: true };
delete expectedAwsAccount.createdAt;
expect(store.list[0]).toMatchObject(expectedAwsAccount);
});

it('should not add an Aws Account', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import { createForm } from '../../helpers/form';

const addBaseAwsAccountFormFields = {
const addUpdateBaseAwsAccountFormFields = {
name: {
label: 'Account Name',
placeholder: 'Type the name of this account',
Expand All @@ -33,7 +33,7 @@ const addBaseAwsAccountFormFields = {
},
};

const addAwsAccountAppStreamFormFields = {
const addUpdateAwsAccountAppStreamFormFields = {
appStreamFleetDesiredInstances: {
label: 'AppStream Fleet Desired Instance',
placeholder: 'Number of users that can concurrently access a workspace through AppStream',
Expand Down Expand Up @@ -73,16 +73,16 @@ const addAwsAccountAppStreamFormFields = {
},
};

function getBaseAddAwsAccountFormFields() {
return addBaseAwsAccountFormFields;
function getBaseAddUpdateAwsAccountFormFields() {
return addUpdateBaseAwsAccountFormFields;
}

function getAddAwsAccountAppStreamFormFields() {
return addAwsAccountAppStreamFormFields;
function getAddUpdateAwsAccountAppStreamFormFields() {
return addUpdateAwsAccountAppStreamFormFields;
}

function getAddAwsAccountForm(fields) {
function getAddUpdateAwsAccountForm(fields) {
return createForm(fields);
}

export { getBaseAddAwsAccountFormFields, getAddAwsAccountAppStreamFormFields, getAddAwsAccountForm };
export { getBaseAddUpdateAwsAccountFormFields, getAddUpdateAwsAccountAppStreamFormFields, getAddUpdateAwsAccountForm };
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import c from 'classnames';

import { createLink } from '@aws-ee/base-ui/dist/helpers/routing';

import { displayWarning } from '@aws-ee/base-ui/dist/helpers/notification';

const { getAccountIdsOfActiveEnvironments } = require('./AccountUtils');

const statusDisplay = {
CURRENT: { color: 'green', display: 'Up-to-Date', spinner: false },
NEEDS_UPDATE: { color: 'orange', display: 'Needs Update', spinner: false },
Expand All @@ -42,13 +46,22 @@ class AccountCard extends React.Component {
runInAction(() => {
this.detailsExpanded = false;
this.isSelected = false;
this.permButtonLoading = false;
});
}

get account() {
return this.props.account;
}

get isAppStreamEnabled() {
return process.env.REACT_APP_IS_APP_STREAM_ENABLED === 'true';
}

get appStreamStatusMismatch() {
return this.isAppStreamEnabled && !this.account.isAppStreamConfigured;
}

get awsAccountsStore() {
return this.props.awsAccountsStore;
}
Expand Down Expand Up @@ -77,24 +90,19 @@ class AccountCard extends React.Component {
};

handleBudgetButton = () => {
const awsAccountId = this.account.id;
this.goto(`/aws-accounts/budget/${awsAccountId}`);
const awsAccountUUID = this.account.id;
this.goto(`/aws-accounts/budget/${awsAccountUUID}`);
};

handleOnboardAccount = () => {
const awsAccountId = this.account.id;
this.goto(`/aws-accounts/onboard/${awsAccountId}`);
};

handleUpdateAccountPerms = () => {
const awsAccountId = this.account.id;
this.goto(`/aws-accounts/onboard/${awsAccountId}`);
};

handlePendingButton = () => {
const awsAccountId = this.account.id;
this.goto(`/aws-accounts/onboard/${awsAccountId}`);
};
handleUpdatePermission() {
const awsAccountUUID = this.account.id;
// If the account needs to be upgraded to support AppStream we need to Update the account with AppStream specific settings, for example: AppStreamImageName
if (this.appStreamStatusMismatch) {
this.goto(`/aws-accounts/update/${awsAccountUUID}/rev/${this.account.rev}`);
} else {
this.goto(`/aws-accounts/onboard/${awsAccountUUID}`);
}
}

render() {
const isSelectable = this.isSelectable; // Internal and external guests can't select studies
Expand Down Expand Up @@ -123,11 +131,6 @@ class AccountCard extends React.Component {
</div>
</Segment>
);

// Checkbox will be added to this segment when functionality for edit/delete users is added
// <div className="mr2" {...onClickAttr}>
// {isSelectable && <Checkbox checked={this.isSelected} style={{ marginTop: '31px' }} />}
// </div>
}

renderHeader(account) {
Expand Down Expand Up @@ -240,17 +243,62 @@ class AccountCard extends React.Component {
);
}

async checkForActiveAccounts() {
runInAction(() => {
this.permButtonLoading = true;
});
const scEnvironmentStore = this.props.scEnvironmentsStore;
const indexesStore = this.props.indexesStore;
const projectsStore = this.props.projectsStore;

await Promise.all([scEnvironmentStore.doLoad(), indexesStore.doLoad(), projectsStore.doLoad()]);
const scEnvs = scEnvironmentStore.list;
const indexes = indexesStore.list;
const projects = projectsStore.list;

const accountHasActiveEnv = getAccountIdsOfActiveEnvironments(scEnvs, projects, indexes).includes(
this.props.account.id,
);
runInAction(() => {
this.permButtonLoading = false;
});

if (accountHasActiveEnv) {
displayWarning('Please terminate all workspaces in this account before upgrading the account');
} else {
this.handleUpdatePermission();
}
}

renderUpdatePermsButton() {
const permissionStatus = this.permissionStatus;
let buttonArgs;
if (permissionStatus === 'NEEDS_UPDATE' || permissionStatus === 'ERRORED')
buttonArgs = { message: 'Update Permissions', color: 'orange', onClick: this.handleUpdateAccountPerms };
buttonArgs = {
message: 'Update Permissions',
color: 'orange',
};
else if (permissionStatus === 'PENDING' || permissionStatus === 'UNKNOWN')
buttonArgs = { message: 'Re-Onboard Account', color: 'red', onClick: this.handlePendingButton };
else buttonArgs = { message: 'Onboard Account', color: 'purple', onClick: this.handleOnboardAccount };
// This button is only displayed if permissionStatus is NEEDS_UPDATE, NEEDS_ONBOARD, or PENDING
buttonArgs = {
message: 'Re-Onboard Account',
color: 'red',
};
else
buttonArgs = {
message: 'Onboard Account',
color: 'purple',
};

buttonArgs.onClick = async () => {
if (this.appStreamStatusMismatch) {
await this.checkForActiveAccounts();
} else {
this.handleUpdatePermission();
}
};

return (
<Button floated="right" color={buttonArgs.color} onClick={buttonArgs.onClick}>
<Button floated="right" color={buttonArgs.color} onClick={buttonArgs.onClick} loading={this.permButtonLoading}>
{buttonArgs.message}
</Button>
);
Expand All @@ -266,6 +314,12 @@ decorate(AccountCard, {
isSelectable: computed,
isSelected: observable,
permissionStatus: computed,
permButtonLoading: observable,
});

export default inject('awsAccountsStore')(withRouter(observer(AccountCard)));
export default inject(
'awsAccountsStore',
'scEnvironmentsStore',
'indexesStore',
'projectsStore',
)(withRouter(observer(AccountCard)));
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import _ from 'lodash';

function getAccountIdsOfActiveEnvironments(scEnvs, projects, indexes) {
const nonActivateStates = ['FAILED', 'TERMINATED', 'UNKNOWN'];
const activeEnvs = scEnvs.filter(env => {
return !nonActivateStates.includes(env.status);
});
const projectToActiveEnvs = _.groupBy(activeEnvs, 'projectId');

const indexIdToAwsAccountId = {};
indexes.forEach(index => {
indexIdToAwsAccountId[index.id] = index.awsAccountId;
});

const projectIdToAwsAccountId = {};
projects.forEach(project => {
projectIdToAwsAccountId[project.id] = indexIdToAwsAccountId[project.indexId];
});

return Object.keys(projectToActiveEnvs).map(projectId => {
return projectIdToAwsAccountId[projectId];
});
}

module.exports = {
getAccountIdsOfActiveEnvironments,
};
Loading

0 comments on commit 47872f2

Please sign in to comment.