diff --git a/app/adapters/application.js b/app/adapters/application.js index 8e43c7b4..8cf7756c 100644 --- a/app/adapters/application.js +++ b/app/adapters/application.js @@ -1,8 +1,13 @@ import JSONAPIAdapter from '@ember-data/adapter/json-api'; import { APPS } from 'website-www/constants/urls'; + export default class ApplicationAdapter extends JSONAPIAdapter { host = APPS.API_BACKEND; + headers = { + 'Content-Type': 'application/json', + }; + ajaxOptions() { const options = super.ajaxOptions(...arguments); options.credentials = 'include'; diff --git a/app/adapters/user.js b/app/adapters/user.js index 82830544..48be8bdc 100644 --- a/app/adapters/user.js +++ b/app/adapters/user.js @@ -7,4 +7,15 @@ export default class UserAdapter extends ApplicationAdapter { } return super.urlForQuery(...arguments); } + + urlForQueryRecord(query) { + if (query.firstname && query.lastname) { + return `${super.urlForQueryRecord(...arguments)}/username`; + } + return super.urlForQueryRecord(...arguments); + } + + urlForUpdateRecord() { + return `${this.host}/users/self`; + } } diff --git a/app/components/signup-steps/step-one.hbs b/app/components/signup-steps/step-one.hbs index 691b2644..4e1170fe 100644 --- a/app/components/signup-steps/step-one.hbs +++ b/app/components/signup-steps/step-one.hbs @@ -34,20 +34,6 @@
{{this.errorMessage.lastname}}
{{/if}} - - \ No newline at end of file diff --git a/app/components/signup-steps/step-one.js b/app/components/signup-steps/step-one.js index 32d04f2c..a072cc48 100644 --- a/app/components/signup-steps/step-one.js +++ b/app/components/signup-steps/step-one.js @@ -9,6 +9,7 @@ import { APPS } from '../../constants/urls'; import { toastNotificationTimeoutOptions } from '../../constants/toast-notification'; export default class SignupStepsStepOneComponent extends Component { @service toast; + @service onboarding; @tracked data = { firstname: '', lastname: '', username: '', role: '' }; @tracked isSignupButtonDisabled = true; @tracked isValid = true; @@ -35,17 +36,11 @@ export default class SignupStepsStepOneComponent extends Component { return ( !!this.data.firstname && !!this.data.lastname && - !!this.data.username && !!this.data.role && this.mavenRoleConfirm ); } else { - return ( - !!this.data.firstname && - !!this.data.lastname && - !!this.data.username && - !!this.data.role - ); + return !!this.data.firstname && !!this.data.lastname && !!this.data.role; } } @@ -118,13 +113,28 @@ export default class SignupStepsStepOneComponent extends Component { } } - @action handleButtonClick() { - this.isSignupButtonDisabled = true; - this.signup(); - localStorage.setItem('role', this.data.role); - } - @action async signup() { + const { username } = await this.onboarding.generateUsername( + this.data.firstname, + this.data.lastname, + ); + + let dataToUpdate = { + username, + first_name: this.data.firstname, + last_name: this.data.lastname, + }; + + if (this.data.role !== 'Developer') { + dataToUpdate.roles = { + maven: this.data.role === 'Maven', + designer: this.data.role === 'Designer', + productmanager: this.data.role === 'Product Manager', + }; + } + + await this.onboarding.signup(dataToUpdate); + localStorage.setItem('role', this.data.role); this.args.incrementStep(); } } diff --git a/app/constants/error-messages.js b/app/constants/error-messages.js new file mode 100644 index 00000000..18adcff3 --- /dev/null +++ b/app/constants/error-messages.js @@ -0,0 +1,4 @@ +export const ERROR_MESSAGES = { + somethingWentWrong: 'Something went wrong!', + usernameGeneration: 'Username cannot be generated', +}; diff --git a/app/models/user.js b/app/models/user.js index d9d4c306..5df03bc5 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -4,7 +4,7 @@ export default class UserModel extends Model { @attr first_name; @attr last_name; @attr username; - @attr('string', { defaultValue: 'active' }) status; + @attr('string', { defaultValue: 'onboarding' }) status; @attr roles; @attr yoe; @attr picture; diff --git a/app/serializers/user.js b/app/serializers/user.js index 4b080943..584e4241 100644 --- a/app/serializers/user.js +++ b/app/serializers/user.js @@ -10,6 +10,7 @@ export default class UserSerializer extends ApplicationSerializer { }; return { error }; } + const data = payload.users.map((user) => { const { id, ...other } = user; return { @@ -21,4 +22,33 @@ export default class UserSerializer extends ApplicationSerializer { const links = { ...payload.links, first: null, last: null }; return { data, links }; } + + normalizeResponse(store, primaryModelClass, payload, id, requestType) { + if (requestType === 'queryRecord' && payload.username) { + return { + data: { + id: payload.username, + type: primaryModelClass.modelName, + attributes: { + username: payload.username, + }, + }, + }; + } else { + return super.normalizeResponse( + store, + primaryModelClass, + payload, + id, + requestType, + ); + } + } + + serialize() { + let json = super.serialize(...arguments); + // Remove 'id' as the user patch API does not accept it + delete json.id; + return json; + } } diff --git a/app/services/onboarding.js b/app/services/onboarding.js new file mode 100644 index 00000000..7b025522 --- /dev/null +++ b/app/services/onboarding.js @@ -0,0 +1,60 @@ +import Service from '@ember/service'; +import { inject as service } from '@ember/service'; +import { TOAST_OPTIONS } from '../constants/toast-options'; +import { ERROR_MESSAGES } from '../constants/error-messages'; + +export default class OnboardingService extends Service { + @service store; + @service toast; + + async signup(dataToUpdate) { + try { + let user = this.store.peekRecord('user', dataToUpdate.username); + + if (!user) { + user = this.store.createRecord('user', {}); + } + + if (dataToUpdate.roles) { + user.set('roles', { + ...user.get('roles'), + ...dataToUpdate.roles, + }); + } + + user.setProperties({ + first_name: dataToUpdate.first_name, + last_name: dataToUpdate.last_name, + }); + + await user.save(); + } catch (error) { + this.toast.error( + ERROR_MESSAGES.somethingWentWrong, + 'error!', + TOAST_OPTIONS, + ); + } + } + + async generateUsername(firstname, lastname) { + try { + const sanitizedFirstname = firstname.toLowerCase(); + const sanitizedLastname = lastname.toLowerCase(); + const user = await this.store.queryRecord('user', { + firstname: sanitizedFirstname, + lastname: sanitizedLastname, + dev: true, + }); + if (user && user.username) { + return user; + } + } catch (err) { + this.toast.error( + ERROR_MESSAGES.usernameGeneration, + 'error!', + TOAST_OPTIONS, + ); + } + } +} diff --git a/tests/integration/components/signup-steps/step-one-test.js b/tests/integration/components/signup-steps/step-one-test.js index 40a47574..e88f0cff 100644 --- a/tests/integration/components/signup-steps/step-one-test.js +++ b/tests/integration/components/signup-steps/step-one-test.js @@ -1,4 +1,4 @@ -import { module, test, skip } from 'qunit'; +import { module, test } from 'qunit'; import { setupRenderingTest } from 'website-www/tests/helpers'; import { render, typeIn, select, click } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; @@ -47,67 +47,6 @@ module('Integration | Component | signup-steps/step-one', function (hooks) { assert.dom('[data-test-input-field]').hasProperty('value', 'shubham'); }); - test('it render disable username input field and disable Generate Username button on signupDetails page', async function (assert) { - assert.expect(19); - - await render(hbs``); - - assert - .dom('[data-test-input-field=username]') - .hasAttribute('name', 'username'); - assert.dom('[data-test-input=username]').hasClass('input-box'); - assert.dom('[data-test-input=username]').hasClass('input-box--btn'); - - assert.dom('[data-test-label=username]').hasClass('label'); - assert.dom('[data-test-label=username]').hasText('Username'); - assert.dom('[data-test-label=username]').hasAttribute('for', 'username'); - - assert.dom('[data-test-required=username]').hasClass('required'); - - assert.dom('[data-test-input-field=username]').hasClass('input__field'); - assert.dom('[data-test-input-field=username]').hasClass('input-disable'); - - assert.dom('[data-test-input-field=username]').hasAttribute('required'); - assert - .dom('[data-test-input-field=username]') - .hasAttribute('name', 'username'); - assert.dom('[data-test-input-field=username]').hasProperty('type', 'text'); - assert - .dom('[data-test-input-field=username]') - .hasAttribute('id', 'username'); - assert - .dom('[data-test-input-field=username]') - .hasProperty('disabled', true); - - assert.dom('[data-test-button=generateUsername]').exists(); - assert - .dom('[data-test-button=generateUsername]') - .hasClass('btn-generateUsername'); - assert - .dom('[data-test-button=generateUsername]') - .hasProperty('type', 'button'); - assert - .dom('[data-test-button=generateUsername]') - .hasText('Generate Username'); - assert - .dom('[data-test-button=generateUsername]') - .hasProperty('disabled', true); - }); - - test('generateUsername button is enabled when firstname and lastname input fields are not empty and valid input', async function (assert) { - assert.expect(1); - this.set('onInput', (e) => { - this.value = e.target.value; - }); - - await render(hbs``); - await typeIn('[data-test-input-field=firstname]', 'shubham'); - await typeIn('[data-test-input-field=lastname]', 'sigdar'); - assert - .dom('[data-test-button=generateUsername]') - .hasProperty('disabled', false); - }); - test('render select your role dropdown on signup details page ', async function (assert) { assert.expect(11); @@ -140,9 +79,7 @@ module('Integration | Component | signup-steps/step-one', function (hooks) { await typeIn('[data-test-input-field=firstname]', 'shubham_1'); await typeIn('[data-test-input-field=lastname]', 'sigdar@'); assert.dom('.error__message').exists(); - assert - .dom('[data-test-button=generateUsername]') - .hasProperty('disabled', true); + assert.dom('[data-test-button=signup]').hasProperty('disabled', true); }); test('it renders label and input checkbox when Maven role is chosen', async function (assert) { @@ -180,17 +117,16 @@ module('Integration | Component | signup-steps/step-one', function (hooks) { assert.dom('[data-test-button=signup]').hasText('Signup'); }); - skip('role based button should be enabled when all required fields are filled', async function (assert) { + test('signup button should be enabled when all required fields are filled', async function (assert) { assert.expect(1); await render(hbs``); await typeIn('[data-test-input-field=firstname]', 'shubham'); await typeIn('[data-test-input-field=lastname]', 'sigdar'); - await click('[data-test-button=generateUsername]'); - select('[data-test-dropdown-field]', 'Maven'); - await click('[data-test-dropdown-option="Maven"]'); + select('[data-test-dropdown-field]', 'Designer'); + await click('[data-test-dropdown-option="Designer"]'); assert.dom('[data-test-button=signup]').hasProperty('disabled', false); }); diff --git a/tests/unit/services/onboarding-test.js b/tests/unit/services/onboarding-test.js new file mode 100644 index 00000000..5656b3c5 --- /dev/null +++ b/tests/unit/services/onboarding-test.js @@ -0,0 +1,106 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Service | onboarding', function (hooks) { + setupTest(hooks); + + test('generateUsername method', async function (assert) { + assert.expect(3); + + let service = this.owner.lookup('service:onboarding'); + let store = this.owner.lookup('service:store'); + + store.queryRecord = () => + Promise.resolve({ + username: 'test-user', + get(key) { + return this[key]; + }, + }); + + assert.step('store.queryRecord called'); + + const user = await service.generateUsername('Test', 'User'); + const username = user?.get('username'); + + assert.strictEqual(username, 'test-user', 'Username is correct'); + + assert.verifySteps(['store.queryRecord called']); + }); + + test('signup method for Developer role', async function (assert) { + assert.expect(4); + + let service = this.owner.lookup('service:onboarding'); + let store = this.owner.lookup('service:store'); + + store.peekRecord = () => null; + + store.createRecord = (modelName, properties) => { + assert.step('store.createRecord called'); + let mockRecord = { + setProperties() { + assert.step('setProperties called'); + }, + save() { + assert.step('save called'); + return Promise.resolve(); + }, + }; + + Object.assign(mockRecord, properties); + + return mockRecord; + }; + + let dataToUpdate = { + username: 'testuser', + }; + + await service.signup(dataToUpdate); + + assert.verifySteps([ + 'store.createRecord called', + 'setProperties called', + 'save called', + ]); + }); + + test('signup method for non-Developer role', async function (assert) { + assert.expect(2); + + let service = this.owner.lookup('service:onboarding'); + let store = this.owner.lookup('service:store'); + + store.peekRecord = () => null; + + store.createRecord = (properties) => { + assert.step('store.createRecord called'); + + let mockRecord = { + setProperties() { + assert.step('setProperties called'); + }, + save() { + assert.step('save called'); + return Promise.resolve(); + }, + }; + + Object.assign(mockRecord, properties); + + return mockRecord; + }; + + let dataToUpdate = { + username: 'testuser', + roles: { + maven: true, + }, + }; + + await service.signup(dataToUpdate); + + assert.verifySteps(['store.createRecord called']); + }); +});