Skip to content

Commit

Permalink
Merge pull request #533 from appuio/guide-new-users
Browse files Browse the repository at this point in the history
Guide first-time-users through initial setup
  • Loading branch information
ccremer authored Apr 18, 2023
2 parents a778435 + c6bdc27 commit 726b035
Show file tree
Hide file tree
Showing 31 changed files with 751 additions and 462 deletions.
2 changes: 1 addition & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default defineConfig({
videosFolder: 'cypress/videos',
screenshotsFolder: 'cypress/screenshots',
fixturesFolder: 'cypress/fixtures',
retries: 2,
retries: process.env['CI'] ? 2 : 0,
e2e: {
baseUrl: 'http://localhost:4200',
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
3 changes: 2 additions & 1 deletion cypress/e2e/billingentities.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('Test billing entity list', () => {
cy.visit('/billingentities');
cy.get('#billingentities-title').should('contain.text', 'Billing');
cy.get('#addButton').should('contain.text', 'Add new Billing');
cy.get('#no-billingentity-message').should('contain.text', 'No billing entities available.');
cy.get('#no-billingentity-message').should('contain.text', 'No billing address available.');
});

it('request failed', () => {
Expand Down Expand Up @@ -91,6 +91,7 @@ describe('no permissions', () => {
cy.visit('/billingentities');
cy.get('h1').should('contain.text', 'Billing');
cy.get('addButton').should('not.exist');
cy.get('#no-billingentity-message').should('contain.text', 'No billing address available.');
});

it('no edit permission', () => {
Expand Down
44 changes: 41 additions & 3 deletions cypress/e2e/billingentity-form.cy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createUser } from '../fixtures/user';
import { BillingEntity, BillingEntityPermissions, BillingEntitySpec } from '../../src/app/types/billing-entity';
import { billingEntityNxt, setBillingEntities } from '../fixtures/billingentities';
import { OrganizationPermissions } from '../../src/app/types/organization';

describe('Test billing entity form elements', () => {
beforeEach(() => {
Expand Down Expand Up @@ -161,7 +162,7 @@ describe('Test billing entity form elements', () => {
});

it('should cancel editing', () => {
setBillingEntities(cy);
setBillingEntities(cy, billingEntityNxt); // give at least 1 item to avoid redirect back to form.

['.p-button-secondary', 'a[appbacklink]'].forEach((cancelSelector) => {
cy.visit('/billingentities/$new?edit=y');
Expand All @@ -170,7 +171,7 @@ describe('Test billing entity form elements', () => {
cy.get(cancelSelector).click();
cy.get('app-billingentity-form').should('not.exist');

cy.get('p-messages').should('contain.text', 'No billing entities available');
cy.get('.text-3xl').should('have.length', 1);
});
});
});
Expand All @@ -186,7 +187,11 @@ describe('Test billing entity create', () => {
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/users/mig', {
body: createUser({ username: 'mig', defaultOrganizationRef: 'nxt' }),
});
cy.setPermission({ verb: 'list', ...BillingEntityPermissions }, { verb: 'create', ...BillingEntityPermissions });
cy.setPermission(
{ verb: 'list', ...BillingEntityPermissions },
{ verb: 'create', ...BillingEntityPermissions },
{ verb: 'list', ...OrganizationPermissions }
);
});

it('should create billing', () => {
Expand Down Expand Up @@ -247,6 +252,39 @@ describe('Test billing entity create', () => {
cy.get('.flex-wrap > .text-900').eq(4).should('contain.text', 'mig [email protected]');
cy.get('.flex-wrap > .text-900').eq(5).should('contain.text', '🇩🇪');
});

it('should forward to organizations if first time', () => {
cy.intercept('POST', '/appuio-api/apis/billing.appuio.io/v1/billingentities', (req) => {
const be: BillingEntity = req.body;
be.metadata.name = 'be-2345';
be.metadata.generateName = '';
req.reply(be);
}).as('createBillingEntity');
setBillingEntities(cy, billingEntityNxt);

cy.visit('/billingentities/$new?edit=y&firstTime=y');
cy.get('#title').should('contain.text', 'New Billing');

cy.get('#displayName').type('➡️ Engineering GmbH');

cy.get('#companyEmail').find('input').type('[email protected],');
cy.get('#phone').type('☎️');
cy.get('#line1').type('📃');
cy.get('#line2').type('📋');
cy.get('#postal').type('🏤');
cy.get('#city').type('🏙️');
cy.get('p-dropdown').click().contains('Switzerland').click();

cy.get('#accountingName').type('mig');

cy.get('button[type="submit"]').should('be.enabled').click();
cy.wait('@createBillingEntity');

cy.get('p-toast').should('contain.text', 'Successfully saved');
cy.url().should('include', '/organizations/$new').should('not.include', '?edit=y');

cy.get('#title').should('contain.text', 'New Organization');
});
});

describe('Test billing entity edit', () => {
Expand Down
213 changes: 138 additions & 75 deletions cypress/e2e/first-time-login.cy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { createUser } from '../fixtures/user';
import { organizationListNxtVshn, setOrganization } from '../fixtures/organization';
import { organizationListNxtVshn, organizationNxt, setOrganization } from '../fixtures/organization';
import { OrganizationPermissions } from '../../src/app/types/organization';
import { createOrganizationMembers } from '../fixtures/organization-members';
import { OrganizationMembersPermissions } from '../../src/app/types/organization-members';
import { InvitationPermissions } from '../../src/app/types/invitation';
import { BillingEntityPermissions } from '../../src/app/types/billing-entity';
import { billingEntityNxt, setBillingEntities } from '../fixtures/billingentities';
import { createInvitation } from '../fixtures/invitations';
import { createOrganizationMembers } from '../fixtures/organization-members';

describe('Test First Time Login', () => {
beforeEach(() => {
Expand All @@ -12,129 +15,189 @@ describe('Test First Time Login', () => {
cy.disableCookieBanner();
});
beforeEach(() => {
// needed for initial getUser request
// we assume we don't have the user object yet.
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/users/mig', {
body: createUser({ username: 'mig', defaultOrganizationRef: 'nxt' }),
statusCode: 403,
});
});

it('join organization', () => {
cy.setPermission({ verb: 'list', ...OrganizationPermissions });
it('join organization if not part of organization', () => {
cy.setPermission(
{ verb: 'list', ...OrganizationPermissions },
{ verb: 'list', ...BillingEntityPermissions },
{ verb: 'create', ...BillingEntityPermissions }
);
setOrganization(cy);
setBillingEntities(cy);
cy.visit('/');
cy.get('.p-dialog-header').should('contain.text', 'Welcome to the APPUiO Cloud Portal');
cy.get('#setDefaultOrganizationDialogButton').should('not.exist');
cy.get('#addBillingDialogButton').should('exist');
cy.get('#addOrganizationDialogButton').should('not.exist');
cy.get('#joinOrganizationDialogButton').click();
cy.get('.p-dialog-header').should('contain.text', 'Join Organization');
});
// some requirements may change, see https://github.com/appuio/cloud-portal/issues/438, skipping until it's clear.
it.skip('add organization', () => {

it('join organization if not part of organization and no billing access', () => {
cy.setPermission({ verb: 'list', ...OrganizationPermissions });
setOrganization(cy);
cy.visit('/');
cy.get('.p-dialog-header').should('contain.text', 'Welcome to the APPUiO Cloud Portal');
cy.get('#addOrganizationDialogButton').click();
cy.get('.text-3xl > .ng-star-inserted').should('contain.text', 'New Organization');
});

it('do not show dialog', () => {
cy.setPermission(
{ verb: 'list', resource: 'organizationmembers', group: 'rbac.appuio.io' },
{ verb: 'list', ...OrganizationPermissions }
);
setOrganization(cy, ...organizationListNxtVshn.items);
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/namespaces/nxt/organizationmembers/members', {
body: createOrganizationMembers({
namespace: 'nxt',
userRefs: [{ name: 'mig' }, { name: 'miw' }],
}),
});
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/namespaces/vshn/organizationmembers/members', {
body: createOrganizationMembers({
namespace: 'vshn',
userRefs: [{ name: 'tobru' }, { name: 'corvus' }],
}),
});
cy.visit('/');
cy.get('.p-dialog-header').should('not.exist');
cy.get('#setDefaultOrganizationDialogButton').should('not.exist');
cy.get('#addBillingDialogButton').should('not.exist');
cy.get('#addOrganizationDialogButton').should('not.exist');
cy.get('#joinOrganizationDialogButton').click();
cy.get('.p-dialog-header').should('contain.text', 'Join Organization');
});

it('do not show dialog when redeeming invitations', () => {
it('add organization if not part of organization but part of billing', () => {
cy.setPermission(
{ verb: 'list', ...OrganizationMembersPermissions },
{ verb: 'list', ...OrganizationPermissions },
{ verb: 'list', ...InvitationPermissions }
{ verb: 'list', ...BillingEntityPermissions },
{ verb: 'create', ...BillingEntityPermissions }
);
setOrganization(cy);
cy.visit('/invitations/uuid');
cy.get('#title').should('contain.text', 'Invitation');
cy.get('.p-dialog-header').should('not.exist');
});

// some requirements may change, see https://github.com/appuio/cloud-portal/issues/438, skipping until it's clear.
it.skip('do not show dialog again', () => {
cy.setPermission({ verb: 'list', ...OrganizationPermissions });
setBillingEntities(cy, billingEntityNxt);
setOrganization(cy);
cy.visit('/');
cy.get('.p-dialog-header').should('contain.text', 'Welcome to the APPUiO Cloud Portal');
cy.get('#joinOrganizationDialogButton').should('exist');
cy.get('#addOrganizationDialogButton').should('exist');

cy.get('label[for=hideFirstTimeLoginDialogCheckbox]').click();
cy.get('#setDefaultOrganizationDialogButton').should('not.exist');
cy.get('#addBillingDialogButton').should('not.exist');
cy.get('#addOrganizationDialogButton').click();
cy.get('.text-3xl > .ng-star-inserted').should('contain.text', 'New Organization');
cy.visit('/teams');
cy.get('#teams-title').should('contain.text', 'Teams');
cy.get('.p-dialog-header').should('not.exist');
});

it('show dialog because no organization contains current username', () => {
it('add billing if not part of organization', () => {
cy.setPermission(
{ verb: 'list', resource: 'organizationmembers', group: 'rbac.appuio.io' },
{ verb: 'list', resource: 'organizations', group: 'rbac.appuio.io' }
{ verb: 'list', ...OrganizationPermissions },
{ verb: 'list', ...BillingEntityPermissions },
{ verb: 'create', ...BillingEntityPermissions }
);
setOrganization(cy, ...organizationListNxtVshn.items);

cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/namespaces/nxt/organizationmembers/members', {
body: createOrganizationMembers({
namespace: 'nxt',
userRefs: [{ name: 'miw' }],
}),
});
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/namespaces/vshn/organizationmembers/members', {
body: createOrganizationMembers({
namespace: 'vshn',
userRefs: [{ name: 'tobru' }, { name: 'corvus' }],
}),
});
setBillingEntities(cy);
setOrganization(cy);
cy.visit('/');
cy.get('.p-dialog-header').should('contain.text', 'Welcome to the APPUiO Cloud Portal');
cy.get('#joinOrganizationDialogButton').should('exist');
cy.get('#addOrganizationDialogButton').should('exist');
cy.get('#setDefaultOrganizationDialogButton').should('not.exist');
cy.get('#addOrganizationDialogButton').should('not.exist');
cy.get('#addBillingDialogButton').click();
cy.get('.text-3xl > .ng-star-inserted').should('contain.text', 'New Billing');
cy.get('.p-dialog-header').should('not.exist');
});

it('show dialog with button to set default org because user has no default organization yet', () => {
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/users/mig', {
body: createUser({ username: 'mig' }),
}).as('getUser');
cy.setPermission(
{ verb: 'list', resource: 'organizationmembers', group: 'rbac.appuio.io' },
{ verb: 'list', resource: 'organizations', group: 'rbac.appuio.io' }
{ verb: 'list', ...OrganizationPermissions },
{ verb: 'list', ...BillingEntityPermissions },
{ verb: 'create', ...BillingEntityPermissions }
);
setOrganization(cy, ...organizationListNxtVshn.items);
setOrganization(cy, organizationNxt);
setBillingEntities(cy, billingEntityNxt);
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/namespaces/nxt/organizationmembers/members', {
body: createOrganizationMembers({
namespace: 'nxt',
userRefs: [{ name: 'mig' }],
}),
});
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/namespaces/vshn/organizationmembers/members', {

cy.visit('/');
cy.wait('@getUser');
cy.get('.p-dialog-header').should('contain.text', 'Welcome to the APPUiO Cloud Portal');
cy.get('#joinOrganizationDialogButton').should('not.exist');
cy.get('#addBillingDialogButton').should('not.exist');
cy.get('#setDefaultOrganizationDialogButton').click();

cy.get('.p-dialog-header').should('not.exist');
cy.get('.text-3xl', { timeout: 1000000 }).should('contain.text', 'User');
});
});

describe('hide dialog', () => {
beforeEach(() => {
cy.setupAuth();
window.localStorage.removeItem('hideFirstTimeLoginDialog');
cy.disableCookieBanner();
});
beforeEach(() => {
// needed for initial getUser request
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/users/mig', {
body: createUser({ username: 'mig' }),
});
});

it('do not show dialog again', () => {
cy.setPermission({ verb: 'list', ...OrganizationPermissions });
setOrganization(cy, organizationNxt);
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/namespaces/nxt/organizationmembers/members', {
body: createOrganizationMembers({
namespace: 'vshn',
userRefs: [{ name: 'tobru' }, { name: 'corvus' }],
namespace: 'nxt',
userRefs: [{ name: 'mig' }, { name: 'miw' }],
}),
});

cy.visit('/');

cy.get('.p-dialog-header').should('contain.text', 'Welcome to the APPUiO Cloud Portal');
cy.get('#joinOrganizationDialogButton').should('not.exist');
cy.get('#addOrganizationDialogButton').should('not.exist');
cy.get('#setDefaultOrganizationDialogButton').should('exist');

cy.get('label[for=hideFirstTimeLoginDialogCheckbox]').click();
cy.get('#setDefaultOrganizationDialogButton').click();

cy.get('.text-3xl').should('contain.text', 'User');

cy.get('app-navbar-item').contains('Organizations').click();
cy.get('#organizations-title').should('contain.text', 'Organizations');
cy.get('.p-dialog-header').should('not.exist');
cy.getAllLocalStorage().then((result) => {
const expected = {
'http://localhost:4200': {
hideFirstTimeLoginDialog: 'true',
},
};
console.debug('expected', expected);
console.debug('result', result);
expect(result).to.deep.eq(expected);
});
});
});

describe('skip dialog', () => {
beforeEach(() => {
cy.setupAuth();
window.localStorage.removeItem('hideFirstTimeLoginDialog');
cy.disableCookieBanner();
});
beforeEach(() => {
// needed for initial getUser request
cy.intercept('GET', 'appuio-api/apis/appuio.io/v1/users/mig', {
body: createUser({ username: 'mig', defaultOrganizationRef: 'nxt' }),
});
});

it('do not show dialog if member of organization', () => {
cy.setPermission({ verb: 'list', ...OrganizationPermissions });
setOrganization(cy, ...organizationListNxtVshn.items);
cy.visit('/');
cy.wait('@organizationList');
cy.get('.p-dialog-header').should('not.exist');
});

it('do not show dialog when redeeming invitations', () => {
cy.setPermission(
{ verb: 'list', ...OrganizationMembersPermissions },
{ verb: 'list', ...OrganizationPermissions },
{ verb: 'list', ...InvitationPermissions }
);
setOrganization(cy);
cy.intercept('GET', 'appuio-api/apis/user.appuio.io/v1/invitations/uuid', {
body: createInvitation({}),
});
cy.visit('/invitations/uuid');
cy.get('#title').should('contain.text', 'Invitation');
cy.get('.p-dialog-header').should('not.exist');
});
});
Loading

0 comments on commit 726b035

Please sign in to comment.