From 3f6170bac84ff73b6c49e6628f78381c8ed4a0ec Mon Sep 17 00:00:00 2001
From: rodentskie
Date: Thu, 11 Sep 2025 09:16:43 +0800
Subject: [PATCH 01/34] fix: tag ci workflow
---
.github/workflows/create-tag.yml | 233 ++++++++++---------------------
1 file changed, 73 insertions(+), 160 deletions(-)
diff --git a/.github/workflows/create-tag.yml b/.github/workflows/create-tag.yml
index 36c0a29ad..d886404c3 100644
--- a/.github/workflows/create-tag.yml
+++ b/.github/workflows/create-tag.yml
@@ -1,197 +1,110 @@
-## REFER DOCS:
-# https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines
-# https://semver.org/
-# https://github.com/semantic-release/semantic-release
-# https://github.com/mathieudutour/github-tag-action#bumping
-
-
+---
################################################
# GITHUB ACTION WORKFLOW NAME
################################################
-name: Practera Release Tag/Branch Creation
-
+name: "Create tag for normal release or hotfix"
################################################
# GITHUB ACTION EVENT TRIGGER
################################################
-on:
+on:
workflow_dispatch:
inputs:
- RELEASE_BRANCH:
- description: '# Branch name that has code to release!'
+ IS_HOTFIX:
+ type: choice
+ description: "Is a hotfix or not."
required: true
- default: ''
+ default: "false"
+ options:
+ - "false"
+ - "true"
HOTFIX_TAG:
- description: '# Optional, if Hotfix Release, then mandatory to input recent tag version (include prefix - v as well) deployed to live otherwise ignore this input'
- default: 'false'
+ description: "Tag to calculate creation of hotfix."
+ default: ""
+ IS_MAJOR:
+ type: choice
+ description: "Is a major release?"
+ required: true
+ default: "false"
+ options:
+ - "false"
+ - "true"
+ IS_MINOR:
+ type: choice
+ description: "Is a minor release?"
+ required: true
+ default: "false"
+ options:
+ - "false"
+ - "true"
+ IS_PATCH:
+ type: choice
+ description: "Is a patch release?"
+ required: true
+ default: "false"
+ options:
+ - "false"
+ - "true"
################################################
-# GITHUB ACTION JOBS
-################################################
+# GITHUB ACTION JOB
+################################################
jobs:
- create-release-tag:
- name: create-release-tag
- runs-on: ubuntu-latest
+ create-tag:
+ permissions: write-all
timeout-minutes: 15
-
+ runs-on: ubuntu-latest
################################################
# GITHUB ACTIONS GLOBAL ENV VARIABLES
################################################
env:
- ENV : live # Valid values are dev,test,live only
- STACK_NAME: live # Valid values are au,us,uk,p2,lf,nu,p1-sandbox,p1-stage,p2-sandbox,shared,p2-usa only
- ROOTSTACK: app-v2
- CFNS3BucketName: devops-cfn-templates
- PRIVATES3BucketName: devops-shared-private
+ ENV: live
STATUSREPORTS3Bucket: deployment-status.practera.com
- VERSION_FILE: package.json
- RELEASE_BRANCH: ${{ github.event.inputs.RELEASE_BRANCH }}
- HOTFIX_TAG: ${{ github.event.inputs.HOTFIX_TAG }}
-
-
+ REGION: ap-southeast-2
+ AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
+ steps:
################################################
# GITHUB REPO CHECKOUT
################################################
- steps:
- uses: actions/checkout@v4
with:
- fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- ref: ${{ github.event.inputs.RELEASE_BRANCH }}
-
+ fetch-depth: 0
+ ref: release/live # branch for release
################################################
-# AWS CLI CONFIGURATION - DEVOPS
-################################################
- - name: Configure AWS credentials from $STACK_NAME account
- uses: aws-actions/configure-aws-credentials@v4
- with:
- aws-access-key-id: ${{ secrets.DEVOPS_AWS_ACCESS_KEY_ID }}
- aws-secret-access-key: ${{ secrets.DEVOPS_AWS_SECRET_ACCESS_KEY }}
- aws-region: ap-southeast-2
-
-
+# AWS credentials setup
################################################
-# NEW TAG DEFINED FOR RELEASE PROCESS
-################################################
- - name: New tag defined for release process
- if: env.HOTFIX_TAG == 'false'
+ - name: Get AWS Organization Number
+ id: AWS_ORG
run: |
- aws s3 cp s3://$STATUSREPORTS3Bucket/scripts/create-tag-release.sh create-tag-release.sh
- chmod +x ./create-tag-release.sh
- ./create-tag-release.sh
-
+ CENTRALIZED=$(echo $AWS_ACCOUNT_ID | jq -r .CENTRALIZED)
+ echo "::add-mask::$CENTRALIZED"
+ echo "CENTRALIZED=$CENTRALIZED" >> $GITHUB_OUTPUT
+ - name: Configure AWS Credentials from Centralized account
+ uses: ./.github/actions/aws-oidc
+ with:
+ role-to-assume: arn:aws:iam::${{ steps.AWS_ORG.outputs.CENTRALIZED }}:role/github-restricted-role-to-assume
+ region: ${{ env.REGION }}
-##########################################################
-# NEW TAG DEFINED FOR RELEASE PROCESS - For Hotfix Release
-###########################################################
- - name: New tag defined for release process
- if: env.HOTFIX_TAG != 'false'
+################################################
+# Download script from s3 bucket
+################################################
+ - name: Download script file
run: |
- aws s3 cp s3://$STATUSREPORTS3Bucket/scripts/create-hotfix-tag-release.sh create-hotfix-tag-release.sh
- chmod +x ./create-hotfix-tag-release.sh
- ./create-hotfix-tag-release.sh
-
+ aws s3 cp s3://$STATUSREPORTS3Bucket/scripts/tag-releasev2.sh tag-releasev2.sh
+ chmod +x ./tag-releasev2.sh
################################################
-# BUMP VERSION AND PUSH TAGS
-################################################
- - name: Bump version and push tag
- id: tag_version
- uses: mathieudutour/github-tag-action@v5.5
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- custom_tag: ${{ env.RELEASE_TAG_VERSION }}
- tag_prefix: v
- release_branches: ${{ github.event.inputs.RELEASE_BRANCH }}
- create_annotated_tag: true
- # custom_release_rules: hotfix:patch,pre-feat:preminor,bug:patch:Bug Fixes,chore:patch:Chores
- dry_run: false
-
-
+# Run script to create tag
################################################
-# CREATE GIT TAGS FOR RELEASE PROCESS
-################################################
- - name: Create a GitHub release
- uses: actions/create-release@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag_name: ${{ steps.tag_version.outputs.new_tag }}
- release_name: Release ${{ steps.tag_version.outputs.new_tag }}
- body: ${{ steps.tag_version.outputs.changelog }}
-
-
-
-# ##########################################################
-# # SLACK NOTIFICATION FOR SUCCESS
-# ##########################################################
- - name: Slack Notification
- if: ${{ success() }} # Pick up events even if the job fails or is canceled.
- uses: 8398a7/action-slack@v3
- env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- MATRIX_CONTEXT: ${{ toJson(matrix) }} # required
- with:
- status: ${{ job.status }}
- author_name: Release ${{ steps.tag_version.outputs.new_tag }} created for ${{ env.ROOTSTACK }} for deployment to ${{ env.ENV }} environemnt in ${{ env.STACK_NAME }} AWS account. git previous tag id ${{ env.CURRENT_TAG_VERSION }}
- mention: 'here'
- if_mention: failure,cancelled
- job_name: create-release-tag # Match the name above.
- fields: repo,commit,eventName,ref,workflow,message,author,job,took
- custom_payload: |
- {
- username: 'GitHub Action CI WorkFlow',
- icon_emoji: ':github:',
- attachments: [{
- color: '${{ job.status }}' === 'success' ? 'good' : ${{ job.status }}' === 'failure' ? 'danger' : 'warning',
- text:
- `${process.env.AS_REPO}\n
- ${process.env.AS_COMMIT}\n
- ${process.env.AS_EVENT_NAME}\n
- @${process.env.AS_REF}\n
- @${process.env.AS_WORKFLOW}\n
- ${process.env.AS_MESSAGE}\n
- ${process.env.AS_AUTHOR}\n
- ${process.env.AS_JOB}\n
- ${process.env.AS_TOOK}`,
- }]
- }
-
-
-
-# ##########################################################
-# # SLACK NOTIFICATION FOR FAILURE
-# ##########################################################
- - name: Slack Notification
- if: ${{ failure() }} # Pick up events even if the job fails or is canceled.
- uses: 8398a7/action-slack@v3
+ - name: Run script
+ run: |
+ ./tag-releasev2.sh
env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- MATRIX_CONTEXT: ${{ toJson(matrix) }} # required
- with:
- status: ${{ job.status }}
- author_name: create tag failed; package.json version id ${{env.NEW_TAG_VERSION}}; git latest tag id ${{env.CURRENT_TAG_VERSION}} mismatch - ${{ env.ERR_MSG }}
- mention: 'here'
- if_mention: failure,cancelled
- job_name: create-release-tag # Match the name above.
- fields: repo,commit,eventName,ref,workflow,message,author,job,took
- custom_payload: |
- {
- username: 'GitHub Action CI WorkFlow',
- icon_emoji: ':github:',
- attachments: [{
- color: '${{ job.status }}' === 'success' ? 'good' : ${{ job.status }}' === 'failure' ? 'danger' : 'warning',
- text:
- `${process.env.AS_REPO}\n
- ${process.env.AS_COMMIT}\n
- ${process.env.AS_EVENT_NAME}\n
- @${process.env.AS_REF}\n
- @${process.env.AS_WORKFLOW}\n
- ${process.env.AS_MESSAGE}\n
- ${process.env.AS_AUTHOR}\n
- ${process.env.AS_JOB}\n
- ${process.env.AS_TOOK}`,
- }]
- }
\ No newline at end of file
+ IS_HOTFIX: ${{ github.event.inputs.IS_HOTFIX }}
+ HOTFIX_TAG: ${{ github.event.inputs.HOTFIX_TAG }}
+ IS_MAJOR: ${{ github.event.inputs.IS_MAJOR }}
+ IS_MINOR: ${{ github.event.inputs.IS_MINOR }}
+ IS_PATCH: ${{ github.event.inputs.IS_PATCH }}
From c012a0c285bcad8f084d204f430772b6946eaa0e Mon Sep 17 00:00:00 2001
From: trtshen
Date: Tue, 21 Oct 2025 14:16:18 +0800
Subject: [PATCH 02/34] hide comment if canComment isn't true
---
.../src/app/components/file-upload/file-upload.component.html | 2 +-
.../multi-team-member-selector.component.html | 2 +-
.../v3/src/app/components/multiple/multiple.component.html | 2 +-
projects/v3/src/app/components/oneof/oneof.component.html | 2 +-
projects/v3/src/app/components/slider/slider.component.html | 3 +--
.../team-member-selector/team-member-selector.component.html | 2 +-
projects/v3/src/app/components/text/text.component.html | 2 +-
7 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/projects/v3/src/app/components/file-upload/file-upload.component.html b/projects/v3/src/app/components/file-upload/file-upload.component.html
index 852e10cd9..d6ff397fe 100644
--- a/projects/v3/src/app/components/file-upload/file-upload.component.html
+++ b/projects/v3/src/app/components/file-upload/file-upload.component.html
@@ -34,7 +34,7 @@
>
-
+
Feedback
diff --git a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html
index c7c6160d3..29111acaf 100644
--- a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html
+++ b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html
@@ -14,7 +14,7 @@
-
+
Feedback
diff --git a/projects/v3/src/app/components/multiple/multiple.component.html b/projects/v3/src/app/components/multiple/multiple.component.html
index 83066219e..a89145b28 100644
--- a/projects/v3/src/app/components/multiple/multiple.component.html
+++ b/projects/v3/src/app/components/multiple/multiple.component.html
@@ -25,7 +25,7 @@ {
-
+
Feedback
diff --git a/projects/v3/src/app/components/oneof/oneof.component.html b/projects/v3/src/app/components/oneof/oneof.component.html
index 80a7008cd..645eef6b0 100644
--- a/projects/v3/src/app/components/oneof/oneof.component.html
+++ b/projects/v3/src/app/components/oneof/oneof.component.html
@@ -25,7 +25,7 @@ {{question.
-
+
Feedback
diff --git a/projects/v3/src/app/components/slider/slider.component.html b/projects/v3/src/app/components/slider/slider.component.html
index b88506f83..a310f2ee0 100644
--- a/projects/v3/src/app/components/slider/slider.component.html
+++ b/projects/v3/src/app/components/slider/slider.component.html
@@ -39,8 +39,7 @@ {{question
-
-
+
diff --git a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html
index d01af9047..b7068a23a 100644
--- a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html
+++ b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html
@@ -17,7 +17,7 @@
-
+
Feedback
diff --git a/projects/v3/src/app/components/text/text.component.html b/projects/v3/src/app/components/text/text.component.html
index b6edcde0e..41d4d1134 100644
--- a/projects/v3/src/app/components/text/text.component.html
+++ b/projects/v3/src/app/components/text/text.component.html
@@ -16,7 +16,7 @@ {{question.n
-
+
Feedback
From c6400e14219f9db81df8c328d4660aaa24f5992b Mon Sep 17 00:00:00 2001
From: Wes Sonnenreich
Date: Thu, 30 Oct 2025 20:31:47 -0400
Subject: [PATCH 03/34] feat: Implement WCAG 2.2 AA compliance improvements
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This commit implements comprehensive accessibility improvements to ensure
the application meets WCAG 2.2 AA compliance standards, making it accessible
to users with disabilities.
## Form Accessibility
- Add visible labels to all form inputs with proper label-input associations
- Implement autocomplete attributes for email, password, and tel fields
- Associate validation errors with form controls using aria-describedby
- Add aria-invalid attributes to invalid form fields
- Add text-based required field indicators (not just asterisks)
- Implement fieldset/legend for grouped form controls (checkboxes, radio buttons)
## Navigation & Keyboard Support
- Add skip navigation links for main content and navigation
- Implement keyboard navigation support (Enter/Space) for all interactive elements
- Convert icon-only buttons to accessible button elements with aria-labels
- Add keyboard event handlers (keydown.enter, keydown.space) to custom interactive elements
## ARIA & Semantic HTML
- Implement proper ARIA tabs pattern (role='tablist', 'tab', 'tabpanel', aria-selected)
- Add role='status' and aria-live='polite' to all loading spinners
- Add role='alert' and aria-live to error messages
- Mark decorative icons with aria-hidden='true'
- Add appropriate ARIA roles to custom components (cards, lists, modals)
## Images & Media
- Improve alt text for meaningful images (avatars, logos, content images)
- Mark decorative images with empty alt and aria-hidden='true'
- Add aria-label to video elements
## Focus Management
- Create global focus indicator styles using :focus-visible pseudo-class
- Ensure visible focus indicators meet WCAG contrast requirements
- Add focus styles for buttons, links, and form elements
## Color & Contrast
- Adjust gray color variables (--practera-60-Percent-gray, --practera-80-Percent-gray)
to meet WCAG AA contrast ratios (4.5:1 for normal text)
## Page Titles
- Implement dynamic page title updates using Angular Title service
- Add descriptive page titles to auth components (Login, Registration, etc.)
## Error Handling
- Associate validation errors with form controls using aria-describedby
- Add aria-invalid to form fields when validation fails
- Create live regions for error announcements with role='alert'
## Components Updated
- Auth components (login, registration, forgot password, reset password)
- Form question components (text, multiple choice, single choice, file upload)
- Assessment component
- Chat components (list, room, info)
- Settings component
- Topic component
- Event detail component
- File display and preview components
- Activity card component
- Tab navigation component
- Contact number form component
- Review rating component
## Files Modified
- src/app/app.component.html (skip links)
- src/app/auth/** (form labels, autocomplete, page titles)
- src/app/questions/** (error association, fieldset/legend)
- src/app/assessment/assessment.component.html (loading spinners, errors)
- src/app/chat/** (icon accessibility, alt text)
- src/app/tabs/tabs.component.html (ARIA tabs pattern)
- src/app/shared/** (icon accessibility, file components)
- src/styles.scss (focus indicators, skip links)
- src/theme/variables.scss (color contrast adjustments)
## Testing Recommendations
- Test with screen readers (NVDA, JAWS, VoiceOver)
- Test keyboard-only navigation
- Verify focus indicators are visible
- Test form validation error announcements
- Verify page titles update correctly
## WCAG 2.2 AA Compliance Status
✅ 2.1.1 Keyboard - All functionality operable via keyboard
✅ 2.4.1 Bypass Blocks - Skip navigation links implemented
✅ 2.4.7 Focus Visible - Focus indicators meet requirements
✅ 3.3.1 Error Identification - Errors associated with form controls
✅ 3.3.2 Labels or Instructions - All inputs have visible labels
✅ 4.1.2 Name, Role, Value - Proper ARIA implementation
✅ 1.1.1 Non-text Content - Improved alt text and aria-hidden usage
✅ 1.4.3 Contrast (Minimum) - Color contrast adjustments made
This commit significantly improves accessibility and ensures the application
is usable by people with disabilities, meeting WCAG 2.2 AA compliance standards.
---
src/app/app.component.html | 6 +-
src/app/assessment/assessment.component.html | 47 +++++--
.../auth-forgot-password.component.html | 11 +-
.../auth-forgot-password.component.ts | 10 +-
.../auth/auth-login/auth-login.component.html | 14 +-
.../auth/auth-login/auth-login.component.ts | 10 +-
.../auth-registration.component.html | 52 ++++++--
.../auth-registration.component.ts | 3 +
.../auth-reset-password.component.html | 33 ++++-
.../auth-reset-password.component.ts | 3 +
.../chat/chat-info/chat-info.component.html | 8 +-
.../chat/chat-list/chat-list.component.html | 4 +-
.../chat/chat-room/chat-room.component.html | 63 ++++++---
.../event-detail/event-detail.component.html | 25 ++--
.../question/question.component.html | 22 +--
src/app/go-mobile/go-mobile.component.html | 4 +-
src/app/overview/home/home.component.html | 4 +-
.../file-display/file-display.component.html | 50 +++++--
src/app/questions/file/file.component.html | 18 +--
.../multiple/multiple.component.html | 42 +++---
src/app/questions/oneof/oneof.component.html | 44 +++---
.../team-member-selector.component.html | 18 ++-
src/app/questions/text/text.component.html | 12 +-
.../review-rating.component.html | 81 +++++++++--
src/app/settings/settings.component.html | 16 +--
.../activity-card.component.html | 8 +-
.../branding-logo.component.html | 2 +-
.../contact-number-form.component.html | 28 +++-
.../shared/filestack/filestack.component.html | 38 ++++--
.../filestack/preview/preview.component.html | 30 +++--
.../switcher-program.component.html | 4 +-
src/app/tabs/tabs.component.html | 126 ++++++++++++++----
src/app/topic/topic.component.html | 42 ++++--
src/styles.scss | 39 ++++++
src/theme/variables.scss | 4 +-
35 files changed, 685 insertions(+), 236 deletions(-)
diff --git a/src/app/app.component.html b/src/app/app.component.html
index d61c23676..fc9d645fd 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,9 +1,13 @@
+
+ Skip to main content
+ Skip to navigation
+
+ }" id="main-content">
diff --git a/src/app/assessment/assessment.component.html b/src/app/assessment/assessment.component.html
index 24f2a1b69..331b319a1 100644
--- a/src/app/assessment/assessment.component.html
+++ b/src/app/assessment/assessment.component.html
@@ -2,7 +2,7 @@
-
+
@@ -10,8 +10,16 @@
'subtitle-2': !isMobile
}">{{pageTitle}}
- {{savingMessage}}
- Save
+ {{savingMessage}}
+ Save
@@ -40,7 +48,7 @@
-
+
Locked by {{ submission.submitterName }}
@@ -51,7 +59,7 @@
-
+
{{ group.name }}
@@ -65,17 +73,26 @@
Reviewer’s feedback
-
+
{{ question.name }}
- *
+ *
-
+
+
+
-
+
@@ -83,7 +100,7 @@ No Answer Submitted
+ )" aria-live="polite">No Answer Submitted
-
+ role="status"
+ aria-live="polite"
+ aria-label="Loading assessment">
-
+
-
+
Enter your email and we'll send you a link that will let you pick a new password.
-
+ Email
+
diff --git a/src/app/auth/auth-forgot-password/auth-forgot-password.component.ts b/src/app/auth/auth-forgot-password/auth-forgot-password.component.ts
index 8fdb38709..92cf150c7 100644
--- a/src/app/auth/auth-forgot-password/auth-forgot-password.component.ts
+++ b/src/app/auth/auth-forgot-password/auth-forgot-password.component.ts
@@ -1,4 +1,5 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { Title } from '@angular/platform-browser';
import { NotificationService } from '@shared/notification/notification.service';
import { UtilsService } from '@services/utils.service';
import { AuthService } from '../auth.service';
@@ -9,18 +10,23 @@ import { NewRelicService } from '@shared/new-relic/new-relic.service';
templateUrl: 'auth-forgot-password.component.html',
styleUrls: ['auth-forgot-password.component.scss']
})
-export class AuthForgotPasswordComponent {
+export class AuthForgotPasswordComponent implements OnInit {
email = '';
// variable to control the label of the button
isSending = false;
constructor(
+ private title: Title,
private notificationService: NotificationService,
private authService: AuthService,
private utils: UtilsService,
private newRelic: NewRelicService
) {}
+ ngOnInit() {
+ this.title.setTitle('Forgot Password - Practera');
+ }
+
async send() {
// basic validation
if (this.email.length < 0 || !this.email) {
diff --git a/src/app/auth/auth-login/auth-login.component.html b/src/app/auth/auth-login/auth-login.component.html
index db8d10c7b..1f47ba58e 100644
--- a/src/app/auth/auth-login/auth-login.component.html
+++ b/src/app/auth/auth-login/auth-login.component.html
@@ -6,27 +6,35 @@
diff --git a/src/app/auth/auth-registration/auth-registration.component.ts b/src/app/auth/auth-registration/auth-registration.component.ts
index 9720d46d0..b0203d538 100644
--- a/src/app/auth/auth-registration/auth-registration.component.ts
+++ b/src/app/auth/auth-registration/auth-registration.component.ts
@@ -1,5 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
+import { Title } from '@angular/platform-browser';
import { UtilsService } from '@services/utils.service';
import { NotificationService } from '@shared/notification/notification.service';
import { Md5 } from 'ts-md5/dist/md5';
@@ -42,6 +43,7 @@ export class AuthRegistrationComponent implements OnInit {
constructor(
private route: ActivatedRoute,
+ private title: Title,
private authService: AuthService,
private utils: UtilsService,
private storage: BrowserStorageService,
@@ -54,6 +56,7 @@ export class AuthRegistrationComponent implements OnInit {
}
ngOnInit() {
+ this.title.setTitle('Registration - Practera');
this.domain =
this.domain.indexOf('127.0.0.1') !== -1 ||
this.domain.indexOf('localhost') !== -1
diff --git a/src/app/auth/auth-reset-password/auth-reset-password.component.html b/src/app/auth/auth-reset-password/auth-reset-password.component.html
index 4ad471c2f..d06d00291 100644
--- a/src/app/auth/auth-reset-password/auth-reset-password.component.html
+++ b/src/app/auth/auth-reset-password/auth-reset-password.component.html
@@ -8,33 +8,54 @@
Please enter a new password for your account.
@@ -21,7 +23,7 @@
-
+
Type your first message!
It's time to start a chat
@@ -32,7 +34,7 @@
-
+
@@ -70,7 +72,7 @@
@@ -96,7 +98,7 @@
-
+
{{ message.fileObject.filename }}
@@ -114,9 +116,9 @@
-
+
{{ whoIsTyping }}
-
+
@@ -124,8 +126,8 @@
-
-
+
+
@@ -153,8 +155,15 @@
-
-
+
+
@@ -163,18 +172,36 @@
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/src/app/event-detail/event-detail.component.html b/src/app/event-detail/event-detail.component.html
index 46900dc41..6687dcc6e 100644
--- a/src/app/event-detail/event-detail.component.html
+++ b/src/app/event-detail/event-detail.component.html
@@ -1,6 +1,8 @@
-
+
+
+
Event
@@ -16,29 +18,36 @@ {{ event.name }}
-
+
{{ getEventDate() }}
-
+
{{ event.videoConference.provider }} Meeting
-
-
+
+
Join {{ event.videoConference.provider }} meeting
ID: {{ event.videoConference.meetingId }}
-
+
-
+
{{ event.location }}
-
+
{{ event.remainingCapacity }} Seats Available Out of {{ event.capacity }}
diff --git a/src/app/fast-feedback/question/question.component.html b/src/app/fast-feedback/question/question.component.html
index 2c790b8f3..4cb784cdd 100644
--- a/src/app/fast-feedback/question/question.component.html
+++ b/src/app/fast-feedback/question/question.component.html
@@ -6,13 +6,19 @@
-
-
-
- {{ choice.title }}
-
-
-
-
+
+ {{ question.title }}
+
+
+
+ {{ choice.title }}
+
+
+
+
+
diff --git a/src/app/go-mobile/go-mobile.component.html b/src/app/go-mobile/go-mobile.component.html
index e047dab1c..02c465cc7 100644
--- a/src/app/go-mobile/go-mobile.component.html
+++ b/src/app/go-mobile/go-mobile.component.html
@@ -9,7 +9,7 @@
-
+
You can now launch Practera on your phone for a better experience. Just click below!
@@ -17,7 +17,7 @@
-
+
Add your mobile number to get the most out of Practera. We will never send you spam!
diff --git a/src/app/overview/home/home.component.html b/src/app/overview/home/home.component.html
index c5e11b97c..68c5a9c4f 100644
--- a/src/app/overview/home/home.component.html
+++ b/src/app/overview/home/home.component.html
@@ -5,7 +5,7 @@
-
+
@@ -70,7 +70,7 @@
- Notifications
+ Notifications
diff --git a/src/app/questions/file/file-display/file-display.component.html b/src/app/questions/file/file-display/file-display.component.html
index 71e61b929..29ffafeb3 100644
--- a/src/app/questions/file/file-display/file-display.component.html
+++ b/src/app/questions/file/file-display/file-display.component.html
@@ -2,14 +2,14 @@
-
+
-
+
@@ -29,15 +29,24 @@
-
- {{ file.filename }}
+
+ {{ file.filename }}
- 0" name="search" (click)="previewFile(file)" color="primary" class="ion-padding-left ion-float-right">
+ 0"
+ type="button"
+ class="icon-button"
+ (click)="previewFile(file)"
+ (keydown.enter)="previewFile(file)"
+ (keydown.space)="previewFile(file); $event.preventDefault()"
+ [attr.aria-label]="'Preview ' + file.filename">
+
+
-
-
+
+
The file you have uploaded contains a virus and is potentially harmful. Please contact your program coordinator
@@ -55,13 +64,13 @@
-
+
-
+
-
+
@@ -78,9 +87,26 @@
-
+
+
+
- 0 && !disabled" name="trash-outline" color="primary" class="ion-float-right" (click)="removeUploadedFile()">
+ 0 && !disabled"
+ type="button"
+ class="icon-button"
+ (click)="removeUploadedFile()"
+ (keydown.enter)="removeUploadedFile()"
+ (keydown.space)="removeUploadedFile(); $event.preventDefault()"
+ [attr.aria-label]="'Remove ' + file.filename">
+
+
diff --git a/src/app/questions/file/file.component.html b/src/app/questions/file/file.component.html
index a644e43ef..c46d7abd6 100644
--- a/src/app/questions/file/file.component.html
+++ b/src/app/questions/file/file.component.html
@@ -25,14 +25,16 @@
-
-
-
+ 0 ? 'file-error-' + question.id : null"
+ [attr.aria-invalid]="errors.length > 0 ? 'true' : null">
+
+
{{error}}
-
-
-
- {{ choice.name }}
-
-
-
-
+ 0 ? 'multiple-error-' + question.id : null">
+ {{ question.name }}
+
+
+
+ {{ choice.name }}
+ 0 ? 'true' : null">
+
+
+
+
-
-
-
+
+
+
{{error}}
diff --git a/src/app/questions/oneof/oneof.component.html b/src/app/questions/oneof/oneof.component.html
index 11af16df6..ae8a8cca6 100644
--- a/src/app/questions/oneof/oneof.component.html
+++ b/src/app/questions/oneof/oneof.component.html
@@ -53,23 +53,35 @@
-
-
-
-
- {{ choice.name }}
-
-
-
-
+ 0 ? 'oneof-error-' + question.id : null">
+ {{ question.name }}
+
+ 0 ? 'true' : null">
+
+
+ {{ choice.name }}
+
+
+
+
+
-
-
-
+
+
+
+
+ {{error}}
+
diff --git a/src/app/questions/team-member-selector/team-member-selector.component.html b/src/app/questions/team-member-selector/team-member-selector.component.html
index 8e60df988..7e6ee721f 100644
--- a/src/app/questions/team-member-selector/team-member-selector.component.html
+++ b/src/app/questions/team-member-selector/team-member-selector.component.html
@@ -39,8 +39,16 @@
-
-
+ 0 ? 'team-member-error-' + question.id : null">
+ {{ question.name }}
+
+ 0 ? 'true' : null">
{{ teamMember.userName }}
@@ -48,6 +56,12 @@
+
+ 0">
+
+ {{error}}
+
+
diff --git a/src/app/questions/text/text.component.html b/src/app/questions/text/text.component.html
index 2e3fc6b36..7f12de3f2 100644
--- a/src/app/questions/text/text.component.html
+++ b/src/app/questions/text/text.component.html
@@ -17,15 +17,19 @@
+
{{ question.name }}
-
+ [disabled]="control.disabled"
+ [attr.aria-describedby]="errors.length > 0 ? 'text-error-' + question.id : null"
+ [attr.aria-invalid]="errors.length > 0 ? 'true' : null">
+
{{error}}
@@ -33,8 +37,10 @@
+
Your answer
+
Your feedback
Rating
How helpful was the feedback?
-
+
Feedback helpfulness rating
+
-
+
@@ -20,17 +33,65 @@
How helpful was the feedback?
Send a quick thank you note!
-
-
Thanks
-
Thank You
-
You are awesome!
-
Great support
-
Very helpful
-
Friendly
+
+ Thanks
+ Thank You
+ You are awesome!
+ Great support
+ Very helpful
+ Friendly
-
+
Personal thank you note
+
diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html
index 9382de2c1..da181be89 100644
--- a/src/app/settings/settings.component.html
+++ b/src/app/settings/settings.component.html
@@ -7,8 +7,8 @@
-
-
+
+
-
-
+
+
-
+
@@ -106,15 +106,15 @@
Support
-
+
Email Help
-
+
Terms of Use
-
+
Logout or Switch Environment
Logout
diff --git a/src/app/shared/components/activity-card/activity-card.component.html b/src/app/shared/components/activity-card/activity-card.component.html
index de9f13556..3d9b8f1c3 100644
--- a/src/app/shared/components/activity-card/activity-card.component.html
+++ b/src/app/shared/components/activity-card/activity-card.component.html
@@ -1,4 +1,8 @@
-
+
+ [attr.aria-hidden]="true">
Activity
{{activity.name}}
diff --git a/src/app/shared/components/branding-logo/branding-logo.component.html b/src/app/shared/components/branding-logo/branding-logo.component.html
index da7810bee..29a1538d9 100644
--- a/src/app/shared/components/branding-logo/branding-logo.component.html
+++ b/src/app/shared/components/branding-logo/branding-logo.component.html
@@ -1 +1 @@
-
+
diff --git a/src/app/shared/components/contact-number-form/contact-number-form.component.html b/src/app/shared/components/contact-number-form/contact-number-form.component.html
index 8c69b3806..65f8acff5 100644
--- a/src/app/shared/components/contact-number-form/contact-number-form.component.html
+++ b/src/app/shared/components/contact-number-form/contact-number-form.component.html
@@ -5,12 +5,22 @@
-
+ Country code
+
{{countryCode.code}} {{contactNumberFormat.masks[countryCode.code].format}}
- Mobile number
+ {{activeCountryModelInfo.countryCode}}
- Mobile number
+
- Country
+
+ (ionChange)="updateCountry()"
+ aria-label="Select country">
{{countryCode.name}}
diff --git a/src/app/shared/filestack/filestack.component.html b/src/app/shared/filestack/filestack.component.html
index 6662efa97..c10517bdd 100644
--- a/src/app/shared/filestack/filestack.component.html
+++ b/src/app/shared/filestack/filestack.component.html
@@ -1,8 +1,15 @@
-
-
-
+
+
+
@@ -13,8 +20,8 @@
- Drag and drop file here or
- UPLOAD FILE
+ Drag and drop file here or
+ UPLOAD FILE
@@ -23,13 +30,13 @@
-
+
-
+
-
+
@@ -40,8 +47,15 @@
{{ uploadingFile.fileName }}
-
-
+
+
@@ -53,11 +67,11 @@
- {{ uploadingFile.uploadSize }} of {{ uploadingFile.fileSize }}
+
{{ uploadingFile.uploadSize }} of {{ uploadingFile.fileSize }}
- {{ uploadingFile.uploadProgress }}%
+ {{ uploadingFile.uploadProgress }}%
diff --git a/src/app/shared/filestack/preview/preview.component.html b/src/app/shared/filestack/preview/preview.component.html
index d033d2209..80c71cd76 100644
--- a/src/app/shared/filestack/preview/preview.component.html
+++ b/src/app/shared/filestack/preview/preview.component.html
@@ -1,26 +1,30 @@
-
+ class="ion-margin-start">
+
+
-
+ class="ion-margin-start">
+
+
-
+
diff --git a/src/app/switcher/switcher-program/switcher-program.component.html b/src/app/switcher/switcher-program/switcher-program.component.html
index 3ac9e790d..a2ab3cc41 100644
--- a/src/app/switcher/switcher-program/switcher-program.component.html
+++ b/src/app/switcher/switcher-program/switcher-program.component.html
@@ -10,7 +10,7 @@
-
+
@@ -19,7 +19,7 @@
-
+
diff --git a/src/app/tabs/tabs.component.html b/src/app/tabs/tabs.component.html
index d751cb035..1a6258291 100644
--- a/src/app/tabs/tabs.component.html
+++ b/src/app/tabs/tabs.component.html
@@ -1,67 +1,139 @@
-
+
-
-
- 0">{{ noOfTodoItems }}
+
+
+ 0" [attr.aria-label]="noOfTodoItems + ' new notifications'">{{ noOfTodoItems }}
Home
-
-
+
+
Events
-
-
+
+
Review
-
-
- 0">{{ noOfChats }}
+
+
+ 0" [attr.aria-label]="noOfChats + ' unread messages'">{{ noOfChats }}
Chat
-
-
+
+
Settings
-
+
-
-
+
0" [attr.aria-label]="noOfTodoItems + ' new notifications'">{{
noOfTodoItems }}
Home
-
-
+
Events
-
-
+
+
Review
-
-
- 0">{{ noOfChats }}
+
+
+ 0" [attr.aria-label]="noOfChats + ' unread messages'">{{ noOfChats }}
Chat
-
-
+
+
Settings
diff --git a/src/app/topic/topic.component.html b/src/app/topic/topic.component.html
index b3feed683..500efcdfa 100644
--- a/src/app/topic/topic.component.html
+++ b/src/app/topic/topic.component.html
@@ -1,9 +1,9 @@
-
-
+
+
@@ -14,7 +14,7 @@
-
+
{{topic.title}}
@@ -43,10 +43,24 @@
{{topic.title}}
- 0" name="search"
- (click)="previewFile(file)" color="primary">
- 0" [href]="file.url" download class="text-right" target="_blank">
-
+ 0"
+ type="button"
+ class="icon-button"
+ (click)="previewFile(file)"
+ (keydown.enter)="previewFile(file)"
+ (keydown.space)="previewFile(file); $event.preventDefault()"
+ [attr.aria-label]="'Preview ' + file.name">
+
+
+ 0"
+ [href]="file.url"
+ download
+ class="text-right"
+ target="_blank"
+ [attr.aria-label]="'Download ' + file.name">
+
@@ -58,10 +72,16 @@
{{topic.title}}
-
+
-
+
diff --git a/src/styles.scss b/src/styles.scss
index 7fdc32dd9..077dc8626 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -565,3 +565,42 @@ use for custom header (*Study perth)
position: absolute;
width: 1px;
}
+
+/******************
+ Focus Styles for WCAG 2.4.7
+******************/
+*:focus-visible {
+ outline: 2px solid var(--ion-color-primary);
+ outline-offset: 2px;
+}
+
+button:focus-visible,
+ion-button:focus-visible,
+a:focus-visible,
+ion-item:focus-visible {
+ outline: 2px solid var(--ion-color-primary);
+ outline-offset: 2px;
+}
+
+/* Ensure focus is visible even when box-shadow is removed */
+ion-button[--box-shadow="none"]:focus-visible,
+.no-box-shadow:focus-visible {
+ outline: 2px solid var(--ion-color-primary);
+ outline-offset: 2px;
+}
+
+/* Skip links - visually hidden until focused */
+.skip-link {
+ position: absolute;
+ top: -40px;
+ left: 0;
+ background: var(--ion-color-primary);
+ color: var(--ion-color-primary-contrast);
+ padding: 8px;
+ text-decoration: none;
+ z-index: 1000;
+}
+
+.skip-link:focus {
+ top: 0;
+}
diff --git a/src/theme/variables.scss b/src/theme/variables.scss
index 8255209f4..5608ed323 100644
--- a/src/theme/variables.scss
+++ b/src/theme/variables.scss
@@ -113,7 +113,9 @@
--practera-surface: #F5F6FA;
--practera-30-Percent-gray: rgba(0, 0, 0, 0.3);
--practera-40-Percent-gray: rgba(0, 0, 0, 0.4);
- --practera-60-Percent-gray: rgba(0, 0, 0, 0.6);
+ /* WCAG AA compliant: 60% gray meets 4.5:1 contrast for normal text on white */
+ --practera-60-Percent-gray: rgba(0, 0, 0, 0.7);
+ /* WCAG AA compliant: 80% gray meets 4.5:1 contrast for normal text on white */
--practera-80-Percent-gray: rgba(0, 0, 0, 0.8);
/** Event Video Conference **/
From acef25fe6923b516c5aaaa90b86508f94ff99a4c Mon Sep 17 00:00:00 2001
From: trtshen
Date: Fri, 31 Oct 2025 09:57:53 +0800
Subject: [PATCH 04/34] cleaned legacy code
---
.../achievements-routing.module.ts | 17 -
.../achievements/achievements.component.html | 58 --
.../achievements/achievements.component.scss | 65 --
.../achievements.component.spec.ts | 155 ----
.../achievements/achievements.component.ts | 70 --
src/app/achievements/achievements.module.ts | 22 -
.../achievements/achievements.service.spec.ts | 155 ----
src/app/achievements/achievements.service.ts | 124 ---
.../activity/activity-routing.component.ts | 6 -
src/app/activity/activity-routing.module.ts | 24 -
src/app/activity/activity.component.html | 98 ---
src/app/activity/activity.component.scss | 10 -
src/app/activity/activity.component.spec.ts | 358 --------
src/app/activity/activity.component.ts | 285 -------
src/app/activity/activity.module.ts | 24 -
src/app/activity/activity.service.spec.ts | 282 ------
src/app/activity/activity.service.ts | 209 -----
src/app/animations.ts | 9 -
src/app/app-routing.module.ts | 84 --
src/app/app.component.html | 13 -
src/app/app.component.spec.ts | 112 ---
src/app/app.component.ts | 170 ----
src/app/app.module.ts | 75 --
.../assessment/assessment-routing.module.ts | 45 -
src/app/assessment/assessment.component.html | 230 -----
src/app/assessment/assessment.component.scss | 95 ---
.../assessment/assessment.component.spec.ts | 754 ----------------
src/app/assessment/assessment.component.ts | 807 ------------------
src/app/assessment/assessment.module.ts | 29 -
src/app/assessment/assessment.service.spec.ts | 568 ------------
src/app/assessment/assessment.service.ts | 451 ----------
.../auth-direct-login.component.html | 10 -
.../auth-direct-login.component.spec.ts | 441 ----------
.../auth-direct-login.component.ts | 234 -----
.../auth-forgot-password.component.html | 32 -
.../auth-forgot-password.component.scss | 0
.../auth-forgot-password.component.spec.ts | 113 ---
.../auth-forgot-password.component.ts | 76 --
.../auth-global-login.component.html | 10 -
.../auth-global-login.component.spec.ts | 119 ---
.../auth-global-login.component.ts | 71 --
.../auth/auth-login/auth-login.component.html | 60 --
.../auth/auth-login/auth-login.component.scss | 0
.../auth-login/auth-login.component.spec.ts | 131 ---
.../auth/auth-login/auth-login.component.ts | 106 ---
.../auth-logout/auth-logout.component.spec.ts | 67 --
.../auth/auth-logout/auth-logout.component.ts | 33 -
.../auth-registration.component.html | 134 ---
.../auth-registration.component.scss | 40 -
.../auth-registration.component.spec.ts | 97 ---
.../auth-registration.component.ts | 334 --------
.../auth-reset-password.component.html | 75 --
.../auth-reset-password.component.scss | 4 -
.../auth-reset-password.component.spec.ts | 173 ----
.../auth-reset-password.component.ts | 136 ---
src/app/auth/auth-routing.module.ts | 64 --
src/app/auth/auth.component.ts | 7 -
src/app/auth/auth.guard.ts | 38 -
src/app/auth/auth.module.ts | 50 --
src/app/auth/auth.service.spec.ts | 280 ------
src/app/auth/auth.service.ts | 387 ---------
src/app/auth/program-selected.guard.ts | 28 -
.../terms-conditions-preview.component.html | 15 -
.../terms-conditions-preview.component.scss | 22 -
...terms-conditions-preview.component.spec.ts | 34 -
.../terms-conditions-preview.component.ts | 29 -
src/app/auth/unauthorized.guard.ts | 35 -
.../chat/chat-info/chat-info.component.html | 50 --
.../chat/chat-info/chat-info.component.scss | 103 ---
.../chat-info/chat-info.component.spec.ts | 131 ---
src/app/chat/chat-info/chat-info.component.ts | 71 --
.../chat/chat-list/chat-list.component.html | 46 -
.../chat/chat-list/chat-list.component.scss | 94 --
.../chat-list/chat-list.component.spec.ts | 160 ----
src/app/chat/chat-list/chat-list.component.ts | 136 ---
.../chat-preview/chat-preview.component.html | 39 -
.../chat-preview/chat-preview.component.scss | 19 -
.../chat-preview.component.spec.ts | 75 --
.../chat-preview/chat-preview.component.ts | 25 -
.../chat/chat-room/chat-room.component.html | 207 -----
.../chat/chat-room/chat-room.component.scss | 295 -------
.../chat-room/chat-room.component.spec.ts | 531 ------------
src/app/chat/chat-room/chat-room.component.ts | 706 ---------------
src/app/chat/chat-routing.module.ts | 29 -
.../chat/chat-view/chat-view.component.html | 26 -
.../chat/chat-view/chat-view.component.scss | 8 -
.../chat-view/chat-view.component.spec.ts | 148 ----
src/app/chat/chat-view/chat-view.component.ts | 64 --
src/app/chat/chat.component.ts | 6 -
src/app/chat/chat.module.ts | 41 -
src/app/chat/chat.service.spec.ts | 420 ---------
src/app/chat/chat.service.ts | 392 ---------
.../device-info/device-info.component.html | 49 --
src/app/device-info/device-info.component.ts | 53 --
.../event-detail/event-detail.component.html | 70 --
.../event-detail/event-detail.component.scss | 35 -
.../event-detail.component.spec.ts | 375 --------
.../event-detail/event-detail.component.ts | 239 ------
src/app/event-detail/event-detail.module.ts | 18 -
.../event-detail/event-detail.service.spec.ts | 68 --
src/app/event-detail/event-detail.service.ts | 42 -
src/app/event-list/event-list.component.html | 113 ---
src/app/event-list/event-list.component.scss | 17 -
.../event-list/event-list.component.spec.ts | 284 ------
src/app/event-list/event-list.component.ts | 373 --------
src/app/event-list/event-list.module.ts | 23 -
src/app/event-list/event-list.service.spec.ts | 377 --------
src/app/event-list/event-list.service.ts | 363 --------
src/app/events/events-routing.component.ts | 6 -
src/app/events/events-routing.module.ts | 22 -
src/app/events/events.component.html | 41 -
src/app/events/events.component.scss | 4 -
src/app/events/events.component.spec.ts | 139 ---
src/app/events/events.component.ts | 75 --
src/app/events/events.module.ts | 23 -
.../fast-feedback-submitter.service.spec.ts | 34 -
.../fast-feedback-submitter.service.ts | 24 -
.../fast-feedback.component.html | 32 -
.../fast-feedback.component.scss | 3 -
.../fast-feedback.component.spec.ts | 187 ----
.../fast-feedback/fast-feedback.component.ts | 97 ---
src/app/fast-feedback/fast-feedback.module.ts | 30 -
.../fast-feedback.service.spec.ts | 143 ----
.../fast-feedback/fast-feedback.service.ts | 106 ---
.../question/question.component.html | 24 -
.../question/question.component.scss | 0
.../question/question.component.ts | 16 -
src/app/go-mobile/go-mobile.component.html | 36 -
src/app/go-mobile/go-mobile.component.scss | 21 -
src/app/go-mobile/go-mobile.component.spec.ts | 193 -----
src/app/go-mobile/go-mobile.component.ts | 143 ----
src/app/go-mobile/go-mobile.module.ts | 22 -
src/app/go-mobile/go-mobile.service.spec.ts | 52 --
src/app/go-mobile/go-mobile.service.ts | 16 -
src/app/overview/home/home.component.html | 107 ---
src/app/overview/home/home.component.scss | 50 --
src/app/overview/home/home.component.spec.ts | 608 -------------
src/app/overview/home/home.component.ts | 286 -------
src/app/overview/home/home.service.spec.ts | 456 ----------
src/app/overview/home/home.service.ts | 381 ---------
.../home/slidable/slidable.component.html | 5 -
.../home/slidable/slidable.component.scss | 5 -
.../home/slidable/slidable.component.spec.ts | 123 ---
.../home/slidable/slidable.component.ts | 64 --
.../home/todo-card/todo-card.component.html | 21 -
.../home/todo-card/todo-card.component.scss | 12 -
.../todo-card/todo-card.component.spec.ts | 75 --
.../home/todo-card/todo-card.component.ts | 22 -
src/app/overview/overview-routing.module.ts | 30 -
src/app/overview/overview.component.html | 26 -
src/app/overview/overview.component.scss | 26 -
src/app/overview/overview.component.spec.ts | 99 ---
src/app/overview/overview.component.ts | 39 -
src/app/overview/overview.module.ts | 38 -
src/app/overview/overview.service.spec.ts | 86 --
src/app/overview/overview.service.ts | 57 --
.../overview/project/project.component.html | 101 ---
.../overview/project/project.component.scss | 206 -----
.../project/project.component.spec.ts | 224 -----
src/app/overview/project/project.component.ts | 204 -----
.../overview/project/project.service.spec.ts | 85 --
src/app/overview/project/project.service.ts | 76 --
.../page-not-found-routing.module.ts | 16 -
.../page-not-found.component.html | 9 -
.../page-not-found.component.scss | 0
.../page-not-found.component.ts | 10 -
.../page-not-found/page-not-found.module.ts | 18 -
.../file-display/file-display.component.html | 115 ---
.../file-display/file-display.component.scss | 34 -
.../file-display.component.spec.ts | 213 -----
.../file-display/file-display.component.ts | 82 --
src/app/questions/file/file.component.html | 78 --
src/app/questions/file/file.component.scss | 15 -
src/app/questions/file/file.component.spec.ts | 118 ---
src/app/questions/file/file.component.ts | 160 ----
.../multiple/multiple.component.html | 111 ---
.../multiple/multiple.component.scss | 37 -
.../multiple/multiple.component.spec.ts | 139 ---
.../questions/multiple/multiple.component.ts | 154 ----
src/app/questions/oneof/oneof.component.html | 111 ---
src/app/questions/oneof/oneof.component.scss | 29 -
.../questions/oneof/oneof.component.spec.ts | 139 ---
src/app/questions/oneof/oneof.component.ts | 142 ---
src/app/questions/questions.component.ts | 17 -
src/app/questions/questions.module.ts | 39 -
.../team-member-selector.component.html | 86 --
.../team-member-selector.component.scss | 17 -
.../team-member-selector.component.spec.ts | 140 ---
.../team-member-selector.component.ts | 127 ---
src/app/questions/text/text.component.html | 65 --
src/app/questions/text/text.component.scss | 9 -
src/app/questions/text/text.component.spec.ts | 116 ---
src/app/questions/text/text.component.ts | 155 ----
.../review-list/review-list.component.html | 66 --
.../review-list/review-list.component.scss | 0
.../review-list/review-list.component.spec.ts | 143 ----
src/app/review-list/review-list.component.ts | 129 ---
src/app/review-list/review-list.module.ts | 18 -
.../review-list/review-list.service.spec.ts | 100 ---
src/app/review-list/review-list.service.ts | 75 --
.../review-rating.component.html | 107 ---
.../review-rating.component.scss | 101 ---
.../review-rating.component.spec.ts | 130 ---
.../review-rating/review-rating.component.ts | 117 ---
src/app/review-rating/review-rating.module.ts | 22 -
.../review-rating.service.spec.ts | 39 -
.../review-rating/review-rating.service.ts | 41 -
src/app/reviews/reviews-routing.component.ts | 6 -
src/app/reviews/reviews-routing.module.ts | 26 -
src/app/reviews/reviews.component.html | 34 -
src/app/reviews/reviews.component.scss | 4 -
src/app/reviews/reviews.component.spec.ts | 80 --
src/app/reviews/reviews.component.ts | 53 --
src/app/reviews/reviews.module.ts | 22 -
src/app/services/router-enter.service.ts | 82 --
src/app/services/shared.service.spec.ts | 181 ----
src/app/services/shared.service.ts | 214 -----
src/app/services/storage.service.spec.ts | 143 ----
src/app/services/storage.service.ts | 183 ----
src/app/services/utils.service.spec.ts | 590 -------------
src/app/services/utils.service.ts | 510 -----------
src/app/services/version-check.service.ts | 58 --
.../settings-embed-routing.component.ts | 6 -
.../settings-embed-routing.module.ts | 27 -
.../settings-embed/settings-embed.module.ts | 25 -
src/app/settings/setting.service.spec.ts | 52 --
src/app/settings/setting.service.ts | 40 -
.../settings/settings-routing.component.ts | 6 -
src/app/settings/settings-routing.module.ts | 24 -
src/app/settings/settings.component.html | 121 ---
src/app/settings/settings.component.scss | 112 ---
src/app/settings/settings.component.spec.ts | 208 -----
src/app/settings/settings.component.ts | 185 ----
src/app/settings/settings.module.ts | 29 -
src/app/shared/apollo/apollo.module.ts | 14 -
src/app/shared/apollo/apollo.service.spec.ts | 32 -
src/app/shared/apollo/apollo.service.ts | 188 ----
.../achievement-badge.component.html | 21 -
.../achievement-badge.component.scss | 49 --
.../achievement-badge.component.spec.ts | 71 --
.../achievement-badge.component.ts | 42 -
.../activity-card.component.html | 37 -
.../activity-card.component.scss | 60 --
.../activity-card.component.spec.ts | 27 -
.../activity-card/activity-card.component.ts | 49 --
.../branding-logo.component.html | 1 -
.../branding-logo/branding-logo.component.ts | 12 -
.../circle-progress.component.html | 37 -
.../circle-progress.component.scss | 56 --
.../circle-progress.component.ts | 123 ---
.../clickable-item.component.html | 32 -
.../clickable-item.component.scss | 3 -
.../clickable-item.component.ts | 18 -
.../contact-number-form.component.html | 80 --
.../contact-number-form.component.scss | 55 --
.../contact-number-form.component.spec.ts | 294 -------
.../contact-number-form.component.ts | 323 -------
.../description/description.component.html | 24 -
.../description/description.component.scss | 48 --
.../description/description.component.spec.ts | 27 -
.../description/description.component.ts | 65 --
.../shared/components/img/img.component.html | 1 -
.../shared/components/img/img.component.scss | 28 -
.../components/img/img.component.spec.ts | 44 -
.../shared/components/img/img.component.ts | 55 --
.../list-item/list-item.component.html | 43 -
.../list-item/list-item.component.scss | 77 --
.../list-item/list-item.component.ts | 30 -
.../unlocking/unlocking.component.html | 543 ------------
.../unlocking/unlocking.component.scss | 434 ----------
.../unlocking/unlocking.component.ts | 13 -
.../autoresize/autoresize.directive.ts | 46 -
.../drag-and-drop.directive.spec.ts | 8 -
.../drag-and-drop/drag-and-drop.directive.ts | 66 --
.../directives/float/float.directive.spec.ts | 194 -----
.../directives/float/float.directive.ts | 71 --
.../shared/filestack/filestack.component.html | 84 --
.../shared/filestack/filestack.component.scss | 78 --
.../filestack/filestack.component.spec.ts | 73 --
.../shared/filestack/filestack.component.ts | 136 ---
src/app/shared/filestack/filestack.module.ts | 33 -
.../filestack/filestack.service.spec.ts | 316 -------
src/app/shared/filestack/filestack.service.ts | 262 ------
.../filestack/preview/preview.component.html | 30 -
.../filestack/preview/preview.component.scss | 7 -
.../preview/preview.component.spec.ts | 87 --
.../filestack/preview/preview.component.ts | 26 -
src/app/shared/new-relic/new-relic.module.ts | 26 -
.../new-relic/new-relic.service.spec.ts | 104 ---
src/app/shared/new-relic/new-relic.service.ts | 92 --
.../ngx-embed-video/ngx-embed-video.module.ts | 28 -
.../ngx-embed-video.service.spec.ts | 191 -----
.../ngx-embed-video.service.ts | 228 -----
.../achievement-pop-up.component.html | 39 -
.../achievement-pop-up.component.scss | 45 -
.../achievement-pop-up.component.spec.ts | 143 ----
.../achievement-pop-up.component.ts | 56 --
.../activity-complete-pop-up.component.html | 11 -
.../activity-complete-pop-up.component.scss | 11 -
...activity-complete-pop-up.component.spec.ts | 54 --
.../activity-complete-pop-up.component.ts | 33 -
...lock-team-assessment-pop-up.component.html | 21 -
...lock-team-assessment-pop-up.component.scss | 33 -
...k-team-assessment-pop-up.component.spec.ts | 46 -
.../lock-team-assessment-pop-up.component.ts | 23 -
.../notification/notification.module.ts | 39 -
.../notification/notification.service.spec.ts | 142 ---
.../notification/notification.service.ts | 189 ----
.../notification/pop-up/pop-up.component.css | 0
.../notification/pop-up/pop-up.component.html | 24 -
.../pop-up/pop-up.component.spec.ts | 52 --
.../notification/pop-up/pop-up.component.ts | 33 -
src/app/shared/pusher/pusher.module.spec.ts | 23 -
src/app/shared/pusher/pusher.module.ts | 29 -
src/app/shared/pusher/pusher.service.spec.ts | 385 ---------
src/app/shared/pusher/pusher.service.ts | 334 --------
.../request/request.interceptor.spec.ts | 106 ---
src/app/shared/request/request.interceptor.ts | 56 --
src/app/shared/request/request.module.spec.ts | 28 -
src/app/shared/request/request.module.ts | 40 -
.../shared/request/request.service.spec.ts | 466 ----------
src/app/shared/request/request.service.ts | 370 --------
src/app/shared/shared.module.ts | 76 --
src/app/single-page-deactivate.guard.spec.ts | 42 -
src/app/single-page-deactivate.guard.ts | 17 -
.../switcher-program.component.html | 29 -
.../switcher-program.component.scss | 26 -
.../switcher-program.component.spec.ts | 185 ----
.../switcher-program.component.ts | 86 --
src/app/switcher/switcher-routing.module.ts | 28 -
src/app/switcher/switcher.component.scss | 3 -
src/app/switcher/switcher.component.ts | 7 -
src/app/switcher/switcher.module.ts | 19 -
src/app/switcher/switcher.service.spec.ts | 414 ---------
src/app/switcher/switcher.service.ts | 302 -------
src/app/tabs/tabs-routing.module.ts | 86 --
src/app/tabs/tabs.component.html | 142 ---
src/app/tabs/tabs.component.scss | 8 -
src/app/tabs/tabs.component.spec.ts | 219 -----
src/app/tabs/tabs.component.ts | 158 ----
src/app/tabs/tabs.module.ts | 16 -
src/app/tabs/tabs.service.spec.ts | 120 ---
src/app/tabs/tabs.service.ts | 95 ---
src/app/tasks/tasks-routing.component.ts | 6 -
src/app/tasks/tasks-routing.module.ts | 22 -
src/app/tasks/tasks.component.html | 34 -
src/app/tasks/tasks.component.scss | 0
src/app/tasks/tasks.component.spec.ts | 268 ------
src/app/tasks/tasks.component.ts | 167 ----
src/app/tasks/tasks.module.ts | 24 -
src/app/topic/topic-routing.module.ts | 18 -
src/app/topic/topic.component.html | 92 --
src/app/topic/topic.component.scss | 39 -
src/app/topic/topic.component.spec.ts | 339 --------
src/app/topic/topic.component.ts | 354 --------
src/app/topic/topic.module.ts | 29 -
src/app/topic/topic.service.spec.ts | 158 ----
src/app/topic/topic.service.ts | 119 ---
src/assets/achievement-popup-default.svg | 17 -
src/assets/achievement.svg | 19 -
src/assets/checkmark.svg | 1 -
src/assets/default-experience-image.svg | 10 -
src/assets/geometric-light.png | Bin 200412 -> 0 bytes
src/assets/icon-epmty-chat.svg | 28 -
src/assets/icon/favicon.ico | Bin 87062 -> 0 bytes
src/assets/icon_zoom_24.svg | 9 -
src/assets/img/mobile-bg.png | Bin 15711 -> 0 bytes
src/assets/img/mobile.png | Bin 30181 -> 0 bytes
src/assets/img/sample-badge.png | Bin 11770 -> 0 bytes
src/assets/img/unlocking-background.gif | Bin 713646 -> 0 bytes
src/assets/loading.gif | Bin 85167 -> 0 bytes
src/assets/login-button.svg | 28 -
src/assets/logo.svg | 1 -
src/assets/memphis-light.png | Bin 95637 -> 0 bytes
src/assets/newrelic.js | 2 -
src/assets/paint-light.png | Bin 227790 -> 0 bytes
src/assets/thumbs/practera_icon.png | Bin 13188 -> 0 bytes
src/assets/waves-light.png | Bin 288876 -> 0 bytes
src/environments/.gitignore | 0
src/environments/environment.custom.ts | 42 -
src/environments/environment.local.ts | 53 --
src/environments/environment.sandbox.ts | 54 --
src/environments/environment.trial.ts | 39 -
src/environments/environment.us.ts | 37 -
src/global.scss | 17 -
src/index.html | 54 --
src/index.prod.html | 41 -
src/karma.conf.aws.js | 56 --
src/karma.conf.headless.js | 34 -
src/karma.conf.js | 47 -
src/karma.conf.sonarci.js | 67 --
src/main.ts | 12 -
src/polyfills.ts | 91 --
src/scripts/newrelic.js.dev | 2 -
src/scripts/newrelic.js.prod | 2 -
src/styles.scss | 606 -------------
src/test.ts | 20 -
src/testing/activated-route-stub.ts | 36 -
src/testing/async-observable-helpers.ts | 7 -
src/testing/fixtures.ts | 3 -
.../fixtures/assessment-submissions.ts | 5 -
src/testing/fixtures/chats.ts | 136 ---
src/testing/fixtures/programs.ts | 75 --
src/testing/mocked.service.ts | 169 ----
src/testing/utils.ts | 137 ---
src/theme/pfas/_colors.scss | 29 -
src/theme/pfas/_ionic-components.scss | 29 -
src/theme/pfas/_typography.scss | 114 ---
src/theme/pfas/pfas.scss | 4 -
src/theme/variables.scss | 335 --------
src/tsconfig.app.json | 11 -
src/tsconfig.spec.json | 20 -
src/well-known/apple-app-site-association | 11 -
src/well-known/assetlinks.json | 14 -
414 files changed, 41165 deletions(-)
delete mode 100644 src/app/achievements/achievements-routing.module.ts
delete mode 100644 src/app/achievements/achievements.component.html
delete mode 100644 src/app/achievements/achievements.component.scss
delete mode 100644 src/app/achievements/achievements.component.spec.ts
delete mode 100644 src/app/achievements/achievements.component.ts
delete mode 100644 src/app/achievements/achievements.module.ts
delete mode 100644 src/app/achievements/achievements.service.spec.ts
delete mode 100644 src/app/achievements/achievements.service.ts
delete mode 100644 src/app/activity/activity-routing.component.ts
delete mode 100644 src/app/activity/activity-routing.module.ts
delete mode 100644 src/app/activity/activity.component.html
delete mode 100644 src/app/activity/activity.component.scss
delete mode 100644 src/app/activity/activity.component.spec.ts
delete mode 100644 src/app/activity/activity.component.ts
delete mode 100644 src/app/activity/activity.module.ts
delete mode 100644 src/app/activity/activity.service.spec.ts
delete mode 100644 src/app/activity/activity.service.ts
delete mode 100644 src/app/animations.ts
delete mode 100644 src/app/app-routing.module.ts
delete mode 100644 src/app/app.component.html
delete mode 100644 src/app/app.component.spec.ts
delete mode 100644 src/app/app.component.ts
delete mode 100644 src/app/app.module.ts
delete mode 100644 src/app/assessment/assessment-routing.module.ts
delete mode 100644 src/app/assessment/assessment.component.html
delete mode 100644 src/app/assessment/assessment.component.scss
delete mode 100644 src/app/assessment/assessment.component.spec.ts
delete mode 100644 src/app/assessment/assessment.component.ts
delete mode 100644 src/app/assessment/assessment.module.ts
delete mode 100644 src/app/assessment/assessment.service.spec.ts
delete mode 100644 src/app/assessment/assessment.service.ts
delete mode 100644 src/app/auth/auth-direct-login/auth-direct-login.component.html
delete mode 100644 src/app/auth/auth-direct-login/auth-direct-login.component.spec.ts
delete mode 100644 src/app/auth/auth-direct-login/auth-direct-login.component.ts
delete mode 100644 src/app/auth/auth-forgot-password/auth-forgot-password.component.html
delete mode 100644 src/app/auth/auth-forgot-password/auth-forgot-password.component.scss
delete mode 100644 src/app/auth/auth-forgot-password/auth-forgot-password.component.spec.ts
delete mode 100644 src/app/auth/auth-forgot-password/auth-forgot-password.component.ts
delete mode 100644 src/app/auth/auth-global-login/auth-global-login.component.html
delete mode 100644 src/app/auth/auth-global-login/auth-global-login.component.spec.ts
delete mode 100644 src/app/auth/auth-global-login/auth-global-login.component.ts
delete mode 100644 src/app/auth/auth-login/auth-login.component.html
delete mode 100644 src/app/auth/auth-login/auth-login.component.scss
delete mode 100644 src/app/auth/auth-login/auth-login.component.spec.ts
delete mode 100644 src/app/auth/auth-login/auth-login.component.ts
delete mode 100644 src/app/auth/auth-logout/auth-logout.component.spec.ts
delete mode 100644 src/app/auth/auth-logout/auth-logout.component.ts
delete mode 100644 src/app/auth/auth-registration/auth-registration.component.html
delete mode 100644 src/app/auth/auth-registration/auth-registration.component.scss
delete mode 100644 src/app/auth/auth-registration/auth-registration.component.spec.ts
delete mode 100644 src/app/auth/auth-registration/auth-registration.component.ts
delete mode 100644 src/app/auth/auth-reset-password/auth-reset-password.component.html
delete mode 100644 src/app/auth/auth-reset-password/auth-reset-password.component.scss
delete mode 100644 src/app/auth/auth-reset-password/auth-reset-password.component.spec.ts
delete mode 100644 src/app/auth/auth-reset-password/auth-reset-password.component.ts
delete mode 100644 src/app/auth/auth-routing.module.ts
delete mode 100644 src/app/auth/auth.component.ts
delete mode 100644 src/app/auth/auth.guard.ts
delete mode 100644 src/app/auth/auth.module.ts
delete mode 100644 src/app/auth/auth.service.spec.ts
delete mode 100644 src/app/auth/auth.service.ts
delete mode 100644 src/app/auth/program-selected.guard.ts
delete mode 100644 src/app/auth/terms-conditions-preview/terms-conditions-preview.component.html
delete mode 100644 src/app/auth/terms-conditions-preview/terms-conditions-preview.component.scss
delete mode 100644 src/app/auth/terms-conditions-preview/terms-conditions-preview.component.spec.ts
delete mode 100644 src/app/auth/terms-conditions-preview/terms-conditions-preview.component.ts
delete mode 100644 src/app/auth/unauthorized.guard.ts
delete mode 100644 src/app/chat/chat-info/chat-info.component.html
delete mode 100644 src/app/chat/chat-info/chat-info.component.scss
delete mode 100644 src/app/chat/chat-info/chat-info.component.spec.ts
delete mode 100644 src/app/chat/chat-info/chat-info.component.ts
delete mode 100644 src/app/chat/chat-list/chat-list.component.html
delete mode 100644 src/app/chat/chat-list/chat-list.component.scss
delete mode 100644 src/app/chat/chat-list/chat-list.component.spec.ts
delete mode 100644 src/app/chat/chat-list/chat-list.component.ts
delete mode 100644 src/app/chat/chat-preview/chat-preview.component.html
delete mode 100644 src/app/chat/chat-preview/chat-preview.component.scss
delete mode 100644 src/app/chat/chat-preview/chat-preview.component.spec.ts
delete mode 100644 src/app/chat/chat-preview/chat-preview.component.ts
delete mode 100644 src/app/chat/chat-room/chat-room.component.html
delete mode 100644 src/app/chat/chat-room/chat-room.component.scss
delete mode 100644 src/app/chat/chat-room/chat-room.component.spec.ts
delete mode 100644 src/app/chat/chat-room/chat-room.component.ts
delete mode 100644 src/app/chat/chat-routing.module.ts
delete mode 100644 src/app/chat/chat-view/chat-view.component.html
delete mode 100644 src/app/chat/chat-view/chat-view.component.scss
delete mode 100644 src/app/chat/chat-view/chat-view.component.spec.ts
delete mode 100644 src/app/chat/chat-view/chat-view.component.ts
delete mode 100644 src/app/chat/chat.component.ts
delete mode 100644 src/app/chat/chat.module.ts
delete mode 100644 src/app/chat/chat.service.spec.ts
delete mode 100644 src/app/chat/chat.service.ts
delete mode 100644 src/app/device-info/device-info.component.html
delete mode 100644 src/app/device-info/device-info.component.ts
delete mode 100644 src/app/event-detail/event-detail.component.html
delete mode 100644 src/app/event-detail/event-detail.component.scss
delete mode 100644 src/app/event-detail/event-detail.component.spec.ts
delete mode 100644 src/app/event-detail/event-detail.component.ts
delete mode 100644 src/app/event-detail/event-detail.module.ts
delete mode 100644 src/app/event-detail/event-detail.service.spec.ts
delete mode 100644 src/app/event-detail/event-detail.service.ts
delete mode 100644 src/app/event-list/event-list.component.html
delete mode 100644 src/app/event-list/event-list.component.scss
delete mode 100644 src/app/event-list/event-list.component.spec.ts
delete mode 100644 src/app/event-list/event-list.component.ts
delete mode 100644 src/app/event-list/event-list.module.ts
delete mode 100644 src/app/event-list/event-list.service.spec.ts
delete mode 100644 src/app/event-list/event-list.service.ts
delete mode 100644 src/app/events/events-routing.component.ts
delete mode 100644 src/app/events/events-routing.module.ts
delete mode 100644 src/app/events/events.component.html
delete mode 100644 src/app/events/events.component.scss
delete mode 100644 src/app/events/events.component.spec.ts
delete mode 100644 src/app/events/events.component.ts
delete mode 100644 src/app/events/events.module.ts
delete mode 100644 src/app/fast-feedback/fast-feedback-submitter.service.spec.ts
delete mode 100644 src/app/fast-feedback/fast-feedback-submitter.service.ts
delete mode 100644 src/app/fast-feedback/fast-feedback.component.html
delete mode 100644 src/app/fast-feedback/fast-feedback.component.scss
delete mode 100644 src/app/fast-feedback/fast-feedback.component.spec.ts
delete mode 100644 src/app/fast-feedback/fast-feedback.component.ts
delete mode 100644 src/app/fast-feedback/fast-feedback.module.ts
delete mode 100644 src/app/fast-feedback/fast-feedback.service.spec.ts
delete mode 100644 src/app/fast-feedback/fast-feedback.service.ts
delete mode 100644 src/app/fast-feedback/question/question.component.html
delete mode 100644 src/app/fast-feedback/question/question.component.scss
delete mode 100644 src/app/fast-feedback/question/question.component.ts
delete mode 100644 src/app/go-mobile/go-mobile.component.html
delete mode 100644 src/app/go-mobile/go-mobile.component.scss
delete mode 100644 src/app/go-mobile/go-mobile.component.spec.ts
delete mode 100644 src/app/go-mobile/go-mobile.component.ts
delete mode 100644 src/app/go-mobile/go-mobile.module.ts
delete mode 100644 src/app/go-mobile/go-mobile.service.spec.ts
delete mode 100644 src/app/go-mobile/go-mobile.service.ts
delete mode 100644 src/app/overview/home/home.component.html
delete mode 100644 src/app/overview/home/home.component.scss
delete mode 100644 src/app/overview/home/home.component.spec.ts
delete mode 100644 src/app/overview/home/home.component.ts
delete mode 100644 src/app/overview/home/home.service.spec.ts
delete mode 100644 src/app/overview/home/home.service.ts
delete mode 100644 src/app/overview/home/slidable/slidable.component.html
delete mode 100644 src/app/overview/home/slidable/slidable.component.scss
delete mode 100644 src/app/overview/home/slidable/slidable.component.spec.ts
delete mode 100644 src/app/overview/home/slidable/slidable.component.ts
delete mode 100644 src/app/overview/home/todo-card/todo-card.component.html
delete mode 100644 src/app/overview/home/todo-card/todo-card.component.scss
delete mode 100644 src/app/overview/home/todo-card/todo-card.component.spec.ts
delete mode 100644 src/app/overview/home/todo-card/todo-card.component.ts
delete mode 100644 src/app/overview/overview-routing.module.ts
delete mode 100644 src/app/overview/overview.component.html
delete mode 100644 src/app/overview/overview.component.scss
delete mode 100644 src/app/overview/overview.component.spec.ts
delete mode 100644 src/app/overview/overview.component.ts
delete mode 100644 src/app/overview/overview.module.ts
delete mode 100644 src/app/overview/overview.service.spec.ts
delete mode 100644 src/app/overview/overview.service.ts
delete mode 100644 src/app/overview/project/project.component.html
delete mode 100644 src/app/overview/project/project.component.scss
delete mode 100644 src/app/overview/project/project.component.spec.ts
delete mode 100644 src/app/overview/project/project.component.ts
delete mode 100644 src/app/overview/project/project.service.spec.ts
delete mode 100644 src/app/overview/project/project.service.ts
delete mode 100644 src/app/page-not-found/page-not-found-routing.module.ts
delete mode 100644 src/app/page-not-found/page-not-found.component.html
delete mode 100644 src/app/page-not-found/page-not-found.component.scss
delete mode 100644 src/app/page-not-found/page-not-found.component.ts
delete mode 100644 src/app/page-not-found/page-not-found.module.ts
delete mode 100644 src/app/questions/file/file-display/file-display.component.html
delete mode 100644 src/app/questions/file/file-display/file-display.component.scss
delete mode 100644 src/app/questions/file/file-display/file-display.component.spec.ts
delete mode 100644 src/app/questions/file/file-display/file-display.component.ts
delete mode 100644 src/app/questions/file/file.component.html
delete mode 100644 src/app/questions/file/file.component.scss
delete mode 100644 src/app/questions/file/file.component.spec.ts
delete mode 100644 src/app/questions/file/file.component.ts
delete mode 100644 src/app/questions/multiple/multiple.component.html
delete mode 100644 src/app/questions/multiple/multiple.component.scss
delete mode 100644 src/app/questions/multiple/multiple.component.spec.ts
delete mode 100644 src/app/questions/multiple/multiple.component.ts
delete mode 100644 src/app/questions/oneof/oneof.component.html
delete mode 100644 src/app/questions/oneof/oneof.component.scss
delete mode 100644 src/app/questions/oneof/oneof.component.spec.ts
delete mode 100644 src/app/questions/oneof/oneof.component.ts
delete mode 100644 src/app/questions/questions.component.ts
delete mode 100644 src/app/questions/questions.module.ts
delete mode 100644 src/app/questions/team-member-selector/team-member-selector.component.html
delete mode 100644 src/app/questions/team-member-selector/team-member-selector.component.scss
delete mode 100644 src/app/questions/team-member-selector/team-member-selector.component.spec.ts
delete mode 100644 src/app/questions/team-member-selector/team-member-selector.component.ts
delete mode 100644 src/app/questions/text/text.component.html
delete mode 100644 src/app/questions/text/text.component.scss
delete mode 100644 src/app/questions/text/text.component.spec.ts
delete mode 100644 src/app/questions/text/text.component.ts
delete mode 100644 src/app/review-list/review-list.component.html
delete mode 100644 src/app/review-list/review-list.component.scss
delete mode 100644 src/app/review-list/review-list.component.spec.ts
delete mode 100644 src/app/review-list/review-list.component.ts
delete mode 100644 src/app/review-list/review-list.module.ts
delete mode 100644 src/app/review-list/review-list.service.spec.ts
delete mode 100644 src/app/review-list/review-list.service.ts
delete mode 100644 src/app/review-rating/review-rating.component.html
delete mode 100644 src/app/review-rating/review-rating.component.scss
delete mode 100644 src/app/review-rating/review-rating.component.spec.ts
delete mode 100644 src/app/review-rating/review-rating.component.ts
delete mode 100644 src/app/review-rating/review-rating.module.ts
delete mode 100644 src/app/review-rating/review-rating.service.spec.ts
delete mode 100644 src/app/review-rating/review-rating.service.ts
delete mode 100644 src/app/reviews/reviews-routing.component.ts
delete mode 100644 src/app/reviews/reviews-routing.module.ts
delete mode 100644 src/app/reviews/reviews.component.html
delete mode 100644 src/app/reviews/reviews.component.scss
delete mode 100644 src/app/reviews/reviews.component.spec.ts
delete mode 100644 src/app/reviews/reviews.component.ts
delete mode 100644 src/app/reviews/reviews.module.ts
delete mode 100644 src/app/services/router-enter.service.ts
delete mode 100644 src/app/services/shared.service.spec.ts
delete mode 100644 src/app/services/shared.service.ts
delete mode 100644 src/app/services/storage.service.spec.ts
delete mode 100644 src/app/services/storage.service.ts
delete mode 100644 src/app/services/utils.service.spec.ts
delete mode 100644 src/app/services/utils.service.ts
delete mode 100644 src/app/services/version-check.service.ts
delete mode 100644 src/app/settings-embed/settings-embed-routing.component.ts
delete mode 100644 src/app/settings-embed/settings-embed-routing.module.ts
delete mode 100644 src/app/settings-embed/settings-embed.module.ts
delete mode 100644 src/app/settings/setting.service.spec.ts
delete mode 100644 src/app/settings/setting.service.ts
delete mode 100644 src/app/settings/settings-routing.component.ts
delete mode 100644 src/app/settings/settings-routing.module.ts
delete mode 100644 src/app/settings/settings.component.html
delete mode 100644 src/app/settings/settings.component.scss
delete mode 100644 src/app/settings/settings.component.spec.ts
delete mode 100644 src/app/settings/settings.component.ts
delete mode 100644 src/app/settings/settings.module.ts
delete mode 100644 src/app/shared/apollo/apollo.module.ts
delete mode 100644 src/app/shared/apollo/apollo.service.spec.ts
delete mode 100644 src/app/shared/apollo/apollo.service.ts
delete mode 100644 src/app/shared/components/achievement-badge/achievement-badge.component.html
delete mode 100644 src/app/shared/components/achievement-badge/achievement-badge.component.scss
delete mode 100644 src/app/shared/components/achievement-badge/achievement-badge.component.spec.ts
delete mode 100644 src/app/shared/components/achievement-badge/achievement-badge.component.ts
delete mode 100644 src/app/shared/components/activity-card/activity-card.component.html
delete mode 100644 src/app/shared/components/activity-card/activity-card.component.scss
delete mode 100644 src/app/shared/components/activity-card/activity-card.component.spec.ts
delete mode 100644 src/app/shared/components/activity-card/activity-card.component.ts
delete mode 100644 src/app/shared/components/branding-logo/branding-logo.component.html
delete mode 100644 src/app/shared/components/branding-logo/branding-logo.component.ts
delete mode 100644 src/app/shared/components/circle-progress/circle-progress.component.html
delete mode 100644 src/app/shared/components/circle-progress/circle-progress.component.scss
delete mode 100644 src/app/shared/components/circle-progress/circle-progress.component.ts
delete mode 100644 src/app/shared/components/clickable-item/clickable-item.component.html
delete mode 100644 src/app/shared/components/clickable-item/clickable-item.component.scss
delete mode 100644 src/app/shared/components/clickable-item/clickable-item.component.ts
delete mode 100644 src/app/shared/components/contact-number-form/contact-number-form.component.html
delete mode 100644 src/app/shared/components/contact-number-form/contact-number-form.component.scss
delete mode 100644 src/app/shared/components/contact-number-form/contact-number-form.component.spec.ts
delete mode 100644 src/app/shared/components/contact-number-form/contact-number-form.component.ts
delete mode 100644 src/app/shared/components/description/description.component.html
delete mode 100644 src/app/shared/components/description/description.component.scss
delete mode 100644 src/app/shared/components/description/description.component.spec.ts
delete mode 100644 src/app/shared/components/description/description.component.ts
delete mode 100644 src/app/shared/components/img/img.component.html
delete mode 100644 src/app/shared/components/img/img.component.scss
delete mode 100644 src/app/shared/components/img/img.component.spec.ts
delete mode 100644 src/app/shared/components/img/img.component.ts
delete mode 100644 src/app/shared/components/list-item/list-item.component.html
delete mode 100644 src/app/shared/components/list-item/list-item.component.scss
delete mode 100644 src/app/shared/components/list-item/list-item.component.ts
delete mode 100644 src/app/shared/components/unlocking/unlocking.component.html
delete mode 100644 src/app/shared/components/unlocking/unlocking.component.scss
delete mode 100644 src/app/shared/components/unlocking/unlocking.component.ts
delete mode 100644 src/app/shared/directives/autoresize/autoresize.directive.ts
delete mode 100644 src/app/shared/directives/drag-and-drop/drag-and-drop.directive.spec.ts
delete mode 100644 src/app/shared/directives/drag-and-drop/drag-and-drop.directive.ts
delete mode 100644 src/app/shared/directives/float/float.directive.spec.ts
delete mode 100644 src/app/shared/directives/float/float.directive.ts
delete mode 100644 src/app/shared/filestack/filestack.component.html
delete mode 100644 src/app/shared/filestack/filestack.component.scss
delete mode 100644 src/app/shared/filestack/filestack.component.spec.ts
delete mode 100644 src/app/shared/filestack/filestack.component.ts
delete mode 100644 src/app/shared/filestack/filestack.module.ts
delete mode 100644 src/app/shared/filestack/filestack.service.spec.ts
delete mode 100644 src/app/shared/filestack/filestack.service.ts
delete mode 100644 src/app/shared/filestack/preview/preview.component.html
delete mode 100644 src/app/shared/filestack/preview/preview.component.scss
delete mode 100644 src/app/shared/filestack/preview/preview.component.spec.ts
delete mode 100644 src/app/shared/filestack/preview/preview.component.ts
delete mode 100644 src/app/shared/new-relic/new-relic.module.ts
delete mode 100644 src/app/shared/new-relic/new-relic.service.spec.ts
delete mode 100644 src/app/shared/new-relic/new-relic.service.ts
delete mode 100644 src/app/shared/ngx-embed-video/ngx-embed-video.module.ts
delete mode 100644 src/app/shared/ngx-embed-video/ngx-embed-video.service.spec.ts
delete mode 100644 src/app/shared/ngx-embed-video/ngx-embed-video.service.ts
delete mode 100644 src/app/shared/notification/achievement-pop-up/achievement-pop-up.component.html
delete mode 100644 src/app/shared/notification/achievement-pop-up/achievement-pop-up.component.scss
delete mode 100644 src/app/shared/notification/achievement-pop-up/achievement-pop-up.component.spec.ts
delete mode 100644 src/app/shared/notification/achievement-pop-up/achievement-pop-up.component.ts
delete mode 100644 src/app/shared/notification/activity-complete-pop-up/activity-complete-pop-up.component.html
delete mode 100644 src/app/shared/notification/activity-complete-pop-up/activity-complete-pop-up.component.scss
delete mode 100644 src/app/shared/notification/activity-complete-pop-up/activity-complete-pop-up.component.spec.ts
delete mode 100644 src/app/shared/notification/activity-complete-pop-up/activity-complete-pop-up.component.ts
delete mode 100644 src/app/shared/notification/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.html
delete mode 100644 src/app/shared/notification/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.scss
delete mode 100644 src/app/shared/notification/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.spec.ts
delete mode 100644 src/app/shared/notification/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.ts
delete mode 100644 src/app/shared/notification/notification.module.ts
delete mode 100644 src/app/shared/notification/notification.service.spec.ts
delete mode 100644 src/app/shared/notification/notification.service.ts
delete mode 100644 src/app/shared/notification/pop-up/pop-up.component.css
delete mode 100644 src/app/shared/notification/pop-up/pop-up.component.html
delete mode 100644 src/app/shared/notification/pop-up/pop-up.component.spec.ts
delete mode 100644 src/app/shared/notification/pop-up/pop-up.component.ts
delete mode 100644 src/app/shared/pusher/pusher.module.spec.ts
delete mode 100644 src/app/shared/pusher/pusher.module.ts
delete mode 100644 src/app/shared/pusher/pusher.service.spec.ts
delete mode 100644 src/app/shared/pusher/pusher.service.ts
delete mode 100644 src/app/shared/request/request.interceptor.spec.ts
delete mode 100644 src/app/shared/request/request.interceptor.ts
delete mode 100644 src/app/shared/request/request.module.spec.ts
delete mode 100644 src/app/shared/request/request.module.ts
delete mode 100644 src/app/shared/request/request.service.spec.ts
delete mode 100644 src/app/shared/request/request.service.ts
delete mode 100644 src/app/shared/shared.module.ts
delete mode 100644 src/app/single-page-deactivate.guard.spec.ts
delete mode 100644 src/app/single-page-deactivate.guard.ts
delete mode 100644 src/app/switcher/switcher-program/switcher-program.component.html
delete mode 100644 src/app/switcher/switcher-program/switcher-program.component.scss
delete mode 100644 src/app/switcher/switcher-program/switcher-program.component.spec.ts
delete mode 100644 src/app/switcher/switcher-program/switcher-program.component.ts
delete mode 100644 src/app/switcher/switcher-routing.module.ts
delete mode 100644 src/app/switcher/switcher.component.scss
delete mode 100644 src/app/switcher/switcher.component.ts
delete mode 100644 src/app/switcher/switcher.module.ts
delete mode 100644 src/app/switcher/switcher.service.spec.ts
delete mode 100644 src/app/switcher/switcher.service.ts
delete mode 100644 src/app/tabs/tabs-routing.module.ts
delete mode 100644 src/app/tabs/tabs.component.html
delete mode 100644 src/app/tabs/tabs.component.scss
delete mode 100644 src/app/tabs/tabs.component.spec.ts
delete mode 100644 src/app/tabs/tabs.component.ts
delete mode 100644 src/app/tabs/tabs.module.ts
delete mode 100644 src/app/tabs/tabs.service.spec.ts
delete mode 100644 src/app/tabs/tabs.service.ts
delete mode 100644 src/app/tasks/tasks-routing.component.ts
delete mode 100644 src/app/tasks/tasks-routing.module.ts
delete mode 100644 src/app/tasks/tasks.component.html
delete mode 100644 src/app/tasks/tasks.component.scss
delete mode 100644 src/app/tasks/tasks.component.spec.ts
delete mode 100644 src/app/tasks/tasks.component.ts
delete mode 100644 src/app/tasks/tasks.module.ts
delete mode 100644 src/app/topic/topic-routing.module.ts
delete mode 100644 src/app/topic/topic.component.html
delete mode 100644 src/app/topic/topic.component.scss
delete mode 100644 src/app/topic/topic.component.spec.ts
delete mode 100644 src/app/topic/topic.component.ts
delete mode 100644 src/app/topic/topic.module.ts
delete mode 100644 src/app/topic/topic.service.spec.ts
delete mode 100644 src/app/topic/topic.service.ts
delete mode 100644 src/assets/achievement-popup-default.svg
delete mode 100644 src/assets/achievement.svg
delete mode 100644 src/assets/checkmark.svg
delete mode 100644 src/assets/default-experience-image.svg
delete mode 100644 src/assets/geometric-light.png
delete mode 100644 src/assets/icon-epmty-chat.svg
delete mode 100644 src/assets/icon/favicon.ico
delete mode 100644 src/assets/icon_zoom_24.svg
delete mode 100644 src/assets/img/mobile-bg.png
delete mode 100644 src/assets/img/mobile.png
delete mode 100644 src/assets/img/sample-badge.png
delete mode 100644 src/assets/img/unlocking-background.gif
delete mode 100644 src/assets/loading.gif
delete mode 100644 src/assets/login-button.svg
delete mode 100644 src/assets/logo.svg
delete mode 100644 src/assets/memphis-light.png
delete mode 100644 src/assets/newrelic.js
delete mode 100644 src/assets/paint-light.png
delete mode 100644 src/assets/thumbs/practera_icon.png
delete mode 100644 src/assets/waves-light.png
delete mode 100644 src/environments/.gitignore
delete mode 100644 src/environments/environment.custom.ts
delete mode 100644 src/environments/environment.local.ts
delete mode 100644 src/environments/environment.sandbox.ts
delete mode 100644 src/environments/environment.trial.ts
delete mode 100644 src/environments/environment.us.ts
delete mode 100644 src/global.scss
delete mode 100644 src/index.html
delete mode 100644 src/index.prod.html
delete mode 100644 src/karma.conf.aws.js
delete mode 100644 src/karma.conf.headless.js
delete mode 100644 src/karma.conf.js
delete mode 100644 src/karma.conf.sonarci.js
delete mode 100644 src/main.ts
delete mode 100644 src/polyfills.ts
delete mode 100644 src/scripts/newrelic.js.dev
delete mode 100644 src/scripts/newrelic.js.prod
delete mode 100644 src/styles.scss
delete mode 100644 src/test.ts
delete mode 100644 src/testing/activated-route-stub.ts
delete mode 100644 src/testing/async-observable-helpers.ts
delete mode 100644 src/testing/fixtures.ts
delete mode 100644 src/testing/fixtures/assessment-submissions.ts
delete mode 100644 src/testing/fixtures/chats.ts
delete mode 100644 src/testing/fixtures/programs.ts
delete mode 100644 src/testing/mocked.service.ts
delete mode 100644 src/testing/utils.ts
delete mode 100644 src/theme/pfas/_colors.scss
delete mode 100644 src/theme/pfas/_ionic-components.scss
delete mode 100644 src/theme/pfas/_typography.scss
delete mode 100644 src/theme/pfas/pfas.scss
delete mode 100644 src/theme/variables.scss
delete mode 100644 src/tsconfig.app.json
delete mode 100644 src/tsconfig.spec.json
delete mode 100644 src/well-known/apple-app-site-association
delete mode 100644 src/well-known/assetlinks.json
diff --git a/src/app/achievements/achievements-routing.module.ts b/src/app/achievements/achievements-routing.module.ts
deleted file mode 100644
index 5832ab080..000000000
--- a/src/app/achievements/achievements-routing.module.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
-
-import { AchievementsComponent } from './achievements.component';
-
-const routes: Routes = [
- {
- path: '',
- component: AchievementsComponent
- }
-];
-
-@NgModule({
- imports: [RouterModule.forChild(routes)],
- exports: [RouterModule]
-})
-export class AchievementsRoutingModule {}
diff --git a/src/app/achievements/achievements.component.html b/src/app/achievements/achievements.component.html
deleted file mode 100644
index 72961750e..000000000
--- a/src/app/achievements/achievements.component.html
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
-
-
- Badges
- Badges
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- My Badges
-
-
-
-
-
-
-
-
-
-
-
-
- Total Points : {{ getEarnedPoints }}
- {{userInfo.name}}
-
diff --git a/src/app/achievements/achievements.component.scss b/src/app/achievements/achievements.component.scss
deleted file mode 100644
index 4ac82388e..000000000
--- a/src/app/achievements/achievements.component.scss
+++ /dev/null
@@ -1,65 +0,0 @@
-.total-points {
- .trophy {
- .circle-background {
- width: 72px;
- height: 72px;
- margin: auto;
- ion-avatar {
- width: 72px;
- height: 72px;
- }
- }
- }
- .points {
- text-align: left;
- padding-left: 0 !important;
- p:nth-child(1) {
- margin-bottom: 0 !important;
- }
- p:nth-child(2) {
- margin: 0 !important;
- }
- }
-
- &.desktop {
- justify-content: center !important;
- .points {
- text-align: center;
- }
- }
-}
-
-.badge-col {
- padding: 3px;
-}
-
-.badge-container {
- max-width: 992px;
- margin: auto;
- .badge-col {
- max-width: 224px !important;
- margin-left: 12px;
- margin-right: 12px;
- padding: 0;
- }
-}
-
-@media screen and (max-width: 1023px) {
- .badge-container {
- .badge-col {
- margin-left: 0 !important;
- margin-right: 0 !important;
- padding: 5px;
- }
- }
-}
-
-hr {
- border: 0;
- height: 1px;
- background: #ABAEBB;
-}
-.title-container {
- text-align: left;
- max-width: 955px !important;
-}
\ No newline at end of file
diff --git a/src/app/achievements/achievements.component.spec.ts b/src/app/achievements/achievements.component.spec.ts
deleted file mode 100644
index e52caa8bc..000000000
--- a/src/app/achievements/achievements.component.spec.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { AchievementsService } from './achievements.service';
-import { of } from 'rxjs';
-import { RequestService } from '@shared/request/request.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { UtilsService } from '@app/services/utils.service';
-import { TestUtils } from '@testing/utils';
-
-describe('AchievementsService', () => {
- let service: AchievementsService;
- let requestSpy: jasmine.SpyObj;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- providers: [
- {
- provide: UtilsService,
- useClass: TestUtils,
- },
- AchievementsService,
- {
- provide: RequestService,
- useValue: jasmine.createSpyObj('RequestService', ['get', 'post', 'apiResponseFormatError'])
- },
- {
- provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', {
- getUser: {
- projectId: 1
- }
- })
- },
- ]
- });
- service = TestBed.inject(AchievementsService) as jasmine.SpyObj;
- requestSpy = TestBed.inject(RequestService) as jasmine.SpyObj;
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-
- describe('when testing getAchievements()', () => {
- const requestResponse = {
- success: true,
- data: [
- {
- id: 1,
- name: 'achieve 1',
- description: 'des',
- badge: '',
- points: 100,
- isEarned: true,
- earnedDate: '2019-02-02'
- },
- {
- id: 2,
- name: 'achieve 2',
- description: 'des',
- badge: '',
- points: 200,
- isEarned: false,
- earnedDate: '2019-02-02'
- },
- {
- id: 3,
- name: 'achieve 3',
- description: 'des',
- badge: '',
- points: 300,
- isEarned: true,
- earnedDate: '2019-02-02'
- },
- {
- id: 4,
- name: 'achieve 4',
- description: 'des',
- badge: '',
- points: null,
- isEarned: true,
- earnedDate: '2019-02-02'
- }
- ]
- };
- const achievements = requestResponse.data[0];
- const expected = JSON.parse(JSON.stringify(requestResponse.data)).map(res => {
- return {
- id: res.id,
- name: res.name,
- description: res.description,
- image: res.badge,
- points: res.points,
- isEarned: res.isEarned,
- earnedDate: res.earnedDate
- };
- });
-
- describe('should throw error', () => {
- let tmpRes;
- let errMsg;
- beforeEach(() => {
- tmpRes = JSON.parse(JSON.stringify(requestResponse));
- });
- afterEach(() => {
- requestSpy.get.and.returnValue(of(tmpRes));
- service.getAchievements().subscribe();
- expect(requestSpy.apiResponseFormatError.calls.count()).toBe(1);
- expect(requestSpy.apiResponseFormatError.calls.first().args[0]).toEqual(errMsg);
- });
- it('Achievement format error', () => {
- tmpRes.data = {};
- errMsg = 'Achievement format error';
- });
- it('Achievement object format error', () => {
- tmpRes.data[0] = {};
- errMsg = 'Achievement object format error';
- });
- });
-
- it('should get the correct data', () => {
- requestSpy.get.and.returnValue(of(requestResponse));
- service.getAchievements().subscribe(res => expect(res).toEqual(expected));
- expect(service.totalPoints).toBe(600);
- expect(service.earnedPoints).toBe(400);
- expect(service.isPointsConfigured).toBe(true);
- });
- });
-
- it('should get the correct earned points', () => {
- service.earnedPoints = 123;
- expect(service.getEarnedPoints()).toBe(123);
- });
-
- it('should get the correct total points', () => {
- service.totalPoints = 123;
- expect(service.getTotalPoints()).toBe(123);
- });
-
- it(`should get the correct 'is point configured'`, () => {
- service.isPointsConfigured = true;
- expect(service.getIsPointsConfigured()).toBe(true);
- });
-
- it(`should post the correct data when marking achievement as seen`, () => {
- requestSpy.post.and.returnValue(of({}));
- service.markAchievementAsSeen(11);
- expect(requestSpy.post.calls.count()).toBe(1);
- expect(requestSpy.post.calls.first().args[0].data).toEqual({
- project_id: 1,
- identifier: 'Achievement-11',
- is_done: true
- });
- });
-
-});
diff --git a/src/app/achievements/achievements.component.ts b/src/app/achievements/achievements.component.ts
deleted file mode 100644
index fd1bfdaae..000000000
--- a/src/app/achievements/achievements.component.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { AfterContentChecked, Component, Input, NgZone } from '@angular/core';
-import { Router, ActivatedRoute } from '@angular/router';
-import { AchievementsService, Achievement } from './achievements.service';
-import { UtilsService } from '@services/utils.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { RouterEnter } from '@services/router-enter.service';
-
-@Component({
- selector: 'app-achievements',
- templateUrl: 'achievements.component.html',
- styleUrls: ['achievements.component.scss']
-})
-export class AchievementsComponent extends RouterEnter implements AfterContentChecked {
- routeUrl = '/achievements';
- achievements: Array;
- loadingAchievements = true;
- userInfo = {
- image: '',
- name: ''
- };
-
- constructor (
- public router: Router,
- readonly utils: UtilsService,
- readonly achievementService: AchievementsService,
- private ngZone: NgZone,
- private newRelic: NewRelicService,
- public storage: BrowserStorageService,
- ) {
- super(router);
- }
-
- onEnter() {
- this.userInfo = {
- image: this.storage.get('me').image,
- name: this.storage.get('me').name
- };
- this.loadingAchievements = true;
- this.achievementService.getAchievements().subscribe(
- achievements => {
- this.achievements = achievements;
- this.loadingAchievements = false;
- },
- err => {
- this.newRelic.noticeError(`${JSON.stringify(err)}`);
- }
- );
- }
-
- get isMobile() {
- return this.utils.isMobile();
- }
-
- get getIsPointsConfigured() {
- return this.achievementService.getIsPointsConfigured();
- }
-
- get getEarnedPoints() {
- return this.achievementService.getEarnedPoints();
- }
-
- back() {
- return this.ngZone.run(() => this.router.navigate(['app', 'home']));
- }
-
- ngAfterContentChecked() {
- document.getElementById('badges').focus();
- }
-}
diff --git a/src/app/achievements/achievements.module.ts b/src/app/achievements/achievements.module.ts
deleted file mode 100644
index dea025762..000000000
--- a/src/app/achievements/achievements.module.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { SharedModule } from '@shared/shared.module';
-import { RouterModule } from '@angular/router';
-import { NgModule } from '@angular/core';
-import { AchievementsRoutingModule } from './achievements-routing.module';
-import { AchievementsComponent } from './achievements.component';
-import { AchievementsService } from './achievements.service';
-
-@NgModule({
- imports: [
- SharedModule,
- AchievementsRoutingModule,
- ],
- declarations: [
- AchievementsComponent
- ],
- providers: [
- AchievementsService
- ]
-})
-
-export class AchievementsModule {
-}
diff --git a/src/app/achievements/achievements.service.spec.ts b/src/app/achievements/achievements.service.spec.ts
deleted file mode 100644
index e52caa8bc..000000000
--- a/src/app/achievements/achievements.service.spec.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { AchievementsService } from './achievements.service';
-import { of } from 'rxjs';
-import { RequestService } from '@shared/request/request.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { UtilsService } from '@app/services/utils.service';
-import { TestUtils } from '@testing/utils';
-
-describe('AchievementsService', () => {
- let service: AchievementsService;
- let requestSpy: jasmine.SpyObj;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- providers: [
- {
- provide: UtilsService,
- useClass: TestUtils,
- },
- AchievementsService,
- {
- provide: RequestService,
- useValue: jasmine.createSpyObj('RequestService', ['get', 'post', 'apiResponseFormatError'])
- },
- {
- provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', {
- getUser: {
- projectId: 1
- }
- })
- },
- ]
- });
- service = TestBed.inject(AchievementsService) as jasmine.SpyObj;
- requestSpy = TestBed.inject(RequestService) as jasmine.SpyObj;
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-
- describe('when testing getAchievements()', () => {
- const requestResponse = {
- success: true,
- data: [
- {
- id: 1,
- name: 'achieve 1',
- description: 'des',
- badge: '',
- points: 100,
- isEarned: true,
- earnedDate: '2019-02-02'
- },
- {
- id: 2,
- name: 'achieve 2',
- description: 'des',
- badge: '',
- points: 200,
- isEarned: false,
- earnedDate: '2019-02-02'
- },
- {
- id: 3,
- name: 'achieve 3',
- description: 'des',
- badge: '',
- points: 300,
- isEarned: true,
- earnedDate: '2019-02-02'
- },
- {
- id: 4,
- name: 'achieve 4',
- description: 'des',
- badge: '',
- points: null,
- isEarned: true,
- earnedDate: '2019-02-02'
- }
- ]
- };
- const achievements = requestResponse.data[0];
- const expected = JSON.parse(JSON.stringify(requestResponse.data)).map(res => {
- return {
- id: res.id,
- name: res.name,
- description: res.description,
- image: res.badge,
- points: res.points,
- isEarned: res.isEarned,
- earnedDate: res.earnedDate
- };
- });
-
- describe('should throw error', () => {
- let tmpRes;
- let errMsg;
- beforeEach(() => {
- tmpRes = JSON.parse(JSON.stringify(requestResponse));
- });
- afterEach(() => {
- requestSpy.get.and.returnValue(of(tmpRes));
- service.getAchievements().subscribe();
- expect(requestSpy.apiResponseFormatError.calls.count()).toBe(1);
- expect(requestSpy.apiResponseFormatError.calls.first().args[0]).toEqual(errMsg);
- });
- it('Achievement format error', () => {
- tmpRes.data = {};
- errMsg = 'Achievement format error';
- });
- it('Achievement object format error', () => {
- tmpRes.data[0] = {};
- errMsg = 'Achievement object format error';
- });
- });
-
- it('should get the correct data', () => {
- requestSpy.get.and.returnValue(of(requestResponse));
- service.getAchievements().subscribe(res => expect(res).toEqual(expected));
- expect(service.totalPoints).toBe(600);
- expect(service.earnedPoints).toBe(400);
- expect(service.isPointsConfigured).toBe(true);
- });
- });
-
- it('should get the correct earned points', () => {
- service.earnedPoints = 123;
- expect(service.getEarnedPoints()).toBe(123);
- });
-
- it('should get the correct total points', () => {
- service.totalPoints = 123;
- expect(service.getTotalPoints()).toBe(123);
- });
-
- it(`should get the correct 'is point configured'`, () => {
- service.isPointsConfigured = true;
- expect(service.getIsPointsConfigured()).toBe(true);
- });
-
- it(`should post the correct data when marking achievement as seen`, () => {
- requestSpy.post.and.returnValue(of({}));
- service.markAchievementAsSeen(11);
- expect(requestSpy.post.calls.count()).toBe(1);
- expect(requestSpy.post.calls.first().args[0].data).toEqual({
- project_id: 1,
- identifier: 'Achievement-11',
- is_done: true
- });
- });
-
-});
diff --git a/src/app/achievements/achievements.service.ts b/src/app/achievements/achievements.service.ts
deleted file mode 100644
index fe60a2b21..000000000
--- a/src/app/achievements/achievements.service.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Observable, of } from 'rxjs';
-import { map } from 'rxjs/operators';
-import { RequestService } from '@shared/request/request.service';
-import { UtilsService } from '@services/utils.service';
-import { BrowserStorageService } from '@services/storage.service';
-
-/**
- * @name api
- * @description list of api endpoint involved in this service
- * @type {Object}
- */
-const api = {
- get: {
- achievements: 'api/v2/motivations/achievement/list.json'
- },
- post: {
- todoItem: 'api/v2/motivations/todo_item/edit.json'
- }
-};
-
-export interface Achievement {
- id: number;
- name: string;
- description: string;
- points?: number;
- image?: string;
- isEarned?: boolean;
- earnedDate?: string;
-}
-
-@Injectable({
- providedIn: 'root'
-})
-
-export class AchievementsService {
- earnedPoints = 0;
- totalPoints = 0;
- isPointsConfigured = false;
- constructor(
- private request: RequestService,
- private utils: UtilsService,
- private storage: BrowserStorageService,
- ) { }
-
- getAchievements(order?): Observable {
- if (!order) {
- order = 'asc';
- }
- return this.request.get(api.get.achievements, {
- params: {
- order: order
- }
- })
- .pipe(map(response => {
- return this._normaliseAchievements(response.data);
- })
- );
- }
-
- private _normaliseAchievements(data) {
- if (!Array.isArray(data)) {
- return this.request.apiResponseFormatError('Achievement format error');
- }
- this.earnedPoints = 0;
- this.totalPoints = 0;
- this.isPointsConfigured = false;
- const achievements: Array = [];
- data.forEach(achievement => {
- if (!this.utils.has(achievement, 'id') ||
- !this.utils.has(achievement, 'name') ||
- !this.utils.has(achievement, 'description') ||
- !this.utils.has(achievement, 'badge') ||
- !this.utils.has(achievement, 'points') ||
- !this.utils.has(achievement, 'isEarned') ||
- !this.utils.has(achievement, 'earnedDate')) {
- return this.request.apiResponseFormatError('Achievement object format error');
- }
- achievements.push({
- id: achievement.id,
- name: achievement.name,
- description: achievement.description,
- points: achievement.points,
- image: achievement.badge,
- isEarned: achievement.isEarned,
- earnedDate: achievement.earnedDate,
- });
- if (achievement.points) {
- this.totalPoints += +achievement.points;
- this.isPointsConfigured = true;
- if (achievement.isEarned) {
- this.earnedPoints += +achievement.points;
- }
- }
- });
- return achievements;
- }
-
- getEarnedPoints() {
- return this.earnedPoints;
- }
-
- getTotalPoints() {
- return this.totalPoints;
- }
-
- getIsPointsConfigured() {
- return this.isPointsConfigured;
- }
-
- markAchievementAsSeen(achievementId) {
- const postData = {
- project_id: this.storage.getUser().projectId,
- identifier: 'Achievement-' + achievementId,
- is_done: true
- };
- return this.request.post(
- {
- endPoint: api.post.todoItem,
- data: postData
- }
- ).subscribe();
- }
-}
diff --git a/src/app/activity/activity-routing.component.ts b/src/app/activity/activity-routing.component.ts
deleted file mode 100644
index 6e4e5ca26..000000000
--- a/src/app/activity/activity-routing.component.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
- template: ' '
-})
-export class ActivityRoutingComponent {}
diff --git a/src/app/activity/activity-routing.module.ts b/src/app/activity/activity-routing.module.ts
deleted file mode 100644
index 5ad3695cd..000000000
--- a/src/app/activity/activity-routing.module.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
-
-import { ActivityComponent } from './activity.component';
-import { ActivityRoutingComponent } from './activity-routing.component';
-
-const routes: Routes = [
- {
- path: '',
- component: ActivityRoutingComponent,
- children: [
- {
- path: ':id',
- component: ActivityComponent,
- }
- ]
- }
-];
-
-@NgModule({
- imports: [RouterModule.forChild(routes)],
- exports: [RouterModule]
-})
-export class ActivityRoutingModule {}
diff --git a/src/app/activity/activity.component.html b/src/app/activity/activity.component.html
deleted file mode 100644
index 6f2a042d7..000000000
--- a/src/app/activity/activity.component.html
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
-
-
-
-
- Activity
-
-
-
-
-
-
-
-
-
- Tasks
-
-
-
-
-
-
-
-
-
-
- {{ activity.name }}
-
-
- Tasks
- Get Next
-
-
-
-
-
-
-
-
-
-
-
-
- 0">
-
- Events
-
-
-
-
-
-
- 2"
- lines="none"
- (click)="gotoEvent()"
- aria-labelledby="showMore"
- [isCustomizedCard]="true">
-
- Show More
-
-
-
-
-
diff --git a/src/app/activity/activity.component.scss b/src/app/activity/activity.component.scss
deleted file mode 100644
index 44ade6953..000000000
--- a/src/app/activity/activity.component.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-
-.icon-check {
- font-size: 30px;
-}
-.icon-backward, .icon-forward {
- font-size: 20px !important;
-}
-.task-leading-icon {
- margin-right: 20px;
-}
diff --git a/src/app/activity/activity.component.spec.ts b/src/app/activity/activity.component.spec.ts
deleted file mode 100644
index b28447432..000000000
--- a/src/app/activity/activity.component.spec.ts
+++ /dev/null
@@ -1,358 +0,0 @@
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { HttpClientModule } from '@angular/common/http';
-import { ActivityComponent } from './activity.component';
-import { ActivityService } from './activity.service';
-import { of } from 'rxjs';
-import { Router, ActivatedRoute, convertToParamMap } from '@angular/router';
-import { UtilsService } from '@services/utils.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { SharedModule } from '@shared/shared.module';
-import { FastFeedbackService } from '@app/fast-feedback/fast-feedback.service';
-import { EventListService } from '@app/event-list/event-list.service';
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { BrowserStorageService } from '@services/storage.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { MockRouter } from '@testing/mocked.service';
-import { ApolloService } from '@app/shared/apollo/apollo.service';
-import { TestUtils } from '@testing/utils';
-
-class Page {
- get activityName() {
- return this.query('h1');
- }
- get activityDescription() {
- return this.query('app-description');
- }
- get taskItems() {
- return this.queryAll('#tasks-card clickable-item');
- }
- get taskNames() {
- return this.queryAll('#tasks-card clickable-item h4');
- }
- get eventItems() {
- return this.queryAll('#events-card clickable-item');
- }
- fixture: ComponentFixture;
-
- constructor(fixture: ComponentFixture) {
- this.fixture = fixture;
- }
- //// query helpers ////
- private query(selector: string): T {
- return this.fixture.nativeElement.querySelector(selector);
- }
- private queryAll(selector: string): T[] {
- return this.fixture.nativeElement.querySelectorAll(selector);
- }
-}
-
-describe('ActivityComponent', () => {
- let component: ActivityComponent;
- let fixture: ComponentFixture;
- let page: Page;
- let activitySpy: jasmine.SpyObj;
- let fastFeedbackSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
- let routeStub: Partial;
- let notificationSpy: jasmine.SpyObj;
- let eventSpy: jasmine.SpyObj;
- let utils: UtilsService;
- let storageSpy: jasmine.SpyObj;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- imports: [HttpClientModule, SharedModule, BrowserAnimationsModule],
- declarations: [ActivityComponent],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
- providers: [
- NewRelicService,
- {
- provide: UtilsService,
- useClass: TestUtils,
- },
- {
- provide: ApolloService,
- useValue: jasmine.createSpyObj('ApolloService', ['updateCache']),
- },
- {
- provide: ActivityService,
- useValue: jasmine.createSpyObj('ActivityService', ['getActivity'])
- },
- {
- provide: NotificationService,
- useValue: jasmine.createSpyObj('NotificationService', ['lockTeamAssessmentPopUp', 'popUp'])
- },
- {
- provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', ['getUser', 'getReferrer'])
- },
- {
- provide: Router,
- useClass: MockRouter,
- },
- {
- provide: ActivatedRoute,
- useValue: {
- snapshot: {
- paramMap: convertToParamMap({ id: 1 })
- }
- }
- },
- {
- provide: FastFeedbackService,
- useValue: jasmine.createSpyObj('FastFeedbackService', ['pullFastFeedback'])
- },
- {
- provide: EventListService,
- useValue: jasmine.createSpyObj('EventListService', {
- getEvents: of(true),
- 'isNotActionable': () => true,
- 'timeDisplayed': () => '',
- })
- },
- ],
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(ActivityComponent);
- component = fixture.componentInstance;
- page = new Page(fixture);
- activitySpy = TestBed.inject(ActivityService) as jasmine.SpyObj;
- routeStub = TestBed.inject(ActivatedRoute);
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- notificationSpy = TestBed.inject(NotificationService) as jasmine.SpyObj;
- utils = TestBed.inject(UtilsService) as jasmine.SpyObj;
- fastFeedbackSpy = TestBed.inject(FastFeedbackService) as jasmine.SpyObj;
- eventSpy = TestBed.inject(EventListService) as jasmine.SpyObj;
- storageSpy = TestBed.inject(BrowserStorageService) as jasmine.SpyObj;
- });
-
- const mockActivity = {
- id: 1,
- name: 'test',
- description: 'des',
- tasks: [
- {
- id: 1,
- name: 'topic 1',
- type: 'Topic',
- status: 'done'
- },
- {
- id: 2,
- name: 'asmt 1',
- type: 'Assessment',
- contextId: 21,
- isForTeam: false,
- dueDate: '2019-02-02',
- isOverdue: true,
- isDueToday: false,
- status: 'in progress',
- isLocked: false
- },
- ]
- };
- const mockEvents = [
- {
- id: 1,
- name: 'event 1',
- description: '',
- location: '',
- activityId: 11,
- activityName: 'act name 1',
- startTime: '2029-02-02',
- endTime: '2029-02-02',
- capacity: 20,
- remainingCapacity: 10,
- isBooked: false,
- singleBooking: false,
- canBook: true,
- isPast: false,
- assessment: null,
- allDay: false
- },
- {
- id: 2,
- name: 'event 2',
- description: '',
- location: '',
- activityId: 21,
- activityName: 'act name 2',
- startTime: '2029-02-02',
- endTime: '2029-02-02',
- capacity: 20,
- remainingCapacity: 10,
- isBooked: false,
- singleBooking: false,
- canBook: true,
- isPast: false,
- assessment: null,
- allDay: true
- },
- {
- id: 3,
- name: 'event 3',
- description: '',
- location: '',
- activityId: 31,
- activityName: 'act name 3',
- startTime: '2029-02-02',
- endTime: '2029-02-02',
- capacity: 20,
- remainingCapacity: 10,
- isBooked: false,
- singleBooking: false,
- canBook: true,
- isPast: false,
- assessment: null,
- allDay: false
- }
- ];
- beforeEach(() => {
- activitySpy.getActivity.and.returnValue(of(mockActivity));
- eventSpy.getEvents.and.returnValue(of(mockEvents));
- eventSpy.isNotActionable.and.returnValue(false);
- eventSpy.timeDisplayed.and.returnValue('');
- fastFeedbackSpy.pullFastFeedback.and.returnValue(of({}));
- storageSpy.getUser.and.returnValue({
- teamId: 1
- });
- storageSpy.getReferrer.and.returnValue('');
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-
- describe('when testing constructor()', () => {
- it(`should call getEvents once more if an 'update-event' event triggered`, () => {
- utils.broadcastEvent('update-event', {});
- component.onEnter();
- expect(eventSpy.getEvents.calls.count()).toBe(2);
- expect(component.events.length).toBeGreaterThan(0);
- });
- });
-
- describe('when testing onEnter()', () => {
- it('should get correct activity info and events', () => {
- component.onEnter();
- fixture.detectChanges();
- expect(component.activity).toEqual(mockActivity);
- expect(activitySpy.getActivity.calls.count()).toBe(1);
- expect(component.loadingActivity).toBe(false);
- expect(page.activityName.innerHTML).toEqual(mockActivity.name);
- expect(page.activityDescription).toBeTruthy();
- expect(page.taskItems.length).toBe(mockActivity.tasks.length);
- page.taskNames.forEach((tN, i) => expect(tN.innerHTML).toEqual(mockActivity.tasks[i].name));
- expect(fastFeedbackSpy.pullFastFeedback.calls.count()).toBe(1);
- expect(component.events).toEqual(mockEvents);
- expect(eventSpy.getEvents).toHaveBeenCalledTimes(1);
- // always display 2 events and a "show more"
- expect(page.eventItems.length).toBe(3);
- expect(component.loadingEvents).toBe(false);
- });
- });
-
- describe('when testing back()', () => {
- it('should navigate to the project page', () => {
- component.back();
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['app', 'home']);
- });
- it('should navigate to the external url', () => {
- storageSpy.getReferrer.and.returnValue({
- route: 'activity-task',
- url: 'abc',
- });
- component.back();
- expect(utils.redirectToUrl).toHaveBeenCalled();
- });
- });
-
- describe('when testing goto()', () => {
- it('should navigate to the assessment page correctly', () => {
- component.id = 1;
- component.navigate.subscribe(event =>
- expect(event).toEqual({
- type: 'assessment',
- contextId: 21,
- assessmentId: 2
- })
- );
- component.goto({
- id: 2,
- type: 'Assessment',
- isLocked: false,
- contextId: 21
- });
- });
-
- it('should pop up locked message', () => {
- component.id = 1;
- component.goto({
- id: 2,
- type: 'Assessment',
- isLocked: true,
- contextId: 21,
- submitter: {
- name: 'sub',
- image: 'image'
- }
- });
- expect(notificationSpy.lockTeamAssessmentPopUp.calls.count()).toBe(1);
- expect(notificationSpy.lockTeamAssessmentPopUp.calls.first().args[0]).toEqual({
- name: 'sub',
- image: 'image'
- });
- component.navigate.subscribe(event =>
- expect(event).toEqual({
- type: 'assessment',
- contextId: 21,
- assessmentId: 2
- })
- );
- notificationSpy.lockTeamAssessmentPopUp.calls.first().args[1]({ data: true });
- });
-
- it('should pop up not in team message', () => {
- storageSpy.getUser.and.returnValue({
- teamId: null
- });
- component.goto({
- id: 2,
- type: 'Assessment',
- isForTeam: true,
- isLocked: false
- });
- expect(notificationSpy.popUp.calls.count()).toBe(1);
- expect(notificationSpy.popUp.calls.first().args[1]).toEqual({ message: 'Currently you are not in a team, please reach out to your Administrator or Coordinator to proceed with next steps.' });
- expect(routerSpy.navigate.calls.count()).toBe(0);
- });
-
- it('should navigate to correct topic page', () => {
- component.id = 1;
- component.navigate.subscribe(event =>
- expect(event).toEqual({
- type: 'topic',
- topicId: 2
- })
- );
- component.goto({
- id: 2,
- type: 'Topic'
- });
- });
-
- it('should pop up locked message', () => {
- component.goto({
- id: 2,
- type: 'Locked'
- });
- expect(routerSpy.navigate.calls.count()).toBe(0);
- expect(notificationSpy.popUp.calls.count()).toBe(1);
- expect(notificationSpy.popUp.calls.first().args[1]).toEqual({ message: 'This part of the app is still locked. You can unlock the features by engaging with the app and completing all tasks.' });
- });
- });
-});
diff --git a/src/app/activity/activity.component.ts b/src/app/activity/activity.component.ts
deleted file mode 100644
index 104723e9b..000000000
--- a/src/app/activity/activity.component.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-import { Component, Input, NgZone, Output, EventEmitter } from '@angular/core';
-import { Router } from '@angular/router';
-import { Subscription } from 'rxjs';
-import { ActivityService, Activity } from './activity.service';
-import { UtilsService } from '../services/utils.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { Event, EventListService } from '@app/event-list/event-list.service';
-import { FastFeedbackService } from '../fast-feedback/fast-feedback.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { ApolloService } from '@shared/apollo/apollo.service';
-import { SharedService } from '@app/services/shared.service';
-
-@Component({
- selector: 'app-activity',
- templateUrl: './activity.component.html',
- styleUrls: ['./activity.component.scss']
-})
-export class ActivityComponent {
- @Input() id: number;
- @Input() currentTask;
- @Output() navigate = new EventEmitter();
- // when tasks are ready, emit tasks to the parent component so that the parent component can decide which task to display
- @Output() tasksReady = new EventEmitter();
- getActivity: Subscription;
- getEventPusher: Subscription;
- getEvents: Subscription;
- activity: Activity = {
- id: 0,
- name: '',
- description: '',
- tasks: []
- };
- loadingActivity = true;
- events: Array;
- loadingEvents = true;
-
- constructor(
- public router: Router,
- private activityService: ActivityService,
- private notificationService: NotificationService,
- public storage: BrowserStorageService,
- public eventListService: EventListService,
- public fastFeedbackService: FastFeedbackService,
- private newRelic: NewRelicService,
- private ngZone: NgZone,
- private apolloService: ApolloService,
- readonly sharedService: SharedService,
- readonly utils: UtilsService,
- ) {
-
- // update event list after book/cancel an event
- this.getEventPusher = this.utils.getEvent('update-event').subscribe(
- event => {
- this._getEvents();
- },
- (error) => {
- this.newRelic.noticeError(error);
- }
- );
- }
-
- // force every navigation happen under radar of angular
- private _navigate(direction) {
- if (this.utils.isMobile()) {
- // redirect to topic/assessment page for mobile
- return this.ngZone.run(() => {
- return this.router.navigate(direction);
- });
- } else {
- // emit event to parent component(task component)
- switch (direction[0]) {
- case 'topic':
- this.navigate.emit({
- type: 'topic',
- topicId: direction[2]
- });
- break;
- case 'assessment':
- this.navigate.emit({
- type: 'assessment',
- contextId: direction[3],
- assessmentId: direction[4]
- });
- break;
- default:
- return this.ngZone.run(() => {
- return this.router.navigate(direction);
- });
- }
- }
- }
-
- dueDateFormatter(dueDate) {
- return this.utils.dueDateFormatter(dueDate);
- }
-
- onEnter() {
- this.newRelic.setPageViewName('activity components');
- this.activity = {
- id: 0,
- name: '',
- description: '',
- tasks: []
- };
- this.loadingActivity = true;
- this._getActivity();
- this._getEvents();
- this.fastFeedbackService.pullFastFeedback().subscribe();
- }
-
- private _getActivity() {
- this.getActivity = this.activityService.getActivity(this.id)
- .subscribe(
- activity => {
- if (!activity) {
- // activity is null by default
- return;
- }
- this.activity = activity;
- this.loadingActivity = false;
- this.newRelic.setPageViewName(`Activity ${this.activity.name}, ID: ${this.id}`);
- this.tasksReady.emit(activity.tasks);
- },
- (error) => {
- this.newRelic.noticeError(error);
- }
- );
- }
-
- private _getEvents(events?: Event[]) {
- this.events = events || [];
- if (events === undefined) {
- this.loadingEvents = true;
- this.getEvents = this.eventListService.getEvents(this.id).subscribe(
- res => {
- this.events = res;
- this.loadingEvents = false;
- },
- error => {
- this.newRelic.noticeError(error);
- }
- );
- }
- }
-
- back() {
- const referrer = this.storage.getReferrer();
- if (this.utils.has(referrer, 'url') && referrer.route === 'activity-task') {
- this.newRelic.actionText('Navigating to external return URL from Activity');
- this.utils.redirectToUrl(referrer.url);
- return;
- }
- this._navigate(['app', 'home']);
- this.newRelic.actionText('Back button pressed on Activities Page.');
- }
-
- goto(task) {
- this.newRelic.actionText(`Selected Task (${task.type}): ID ${task.id}`);
-
- switch (task.type) {
- case 'Assessment':
- if (task.isForTeam && !this.storage.getUser().teamId) {
- this.notificationService.popUp('shortMessage', {
- message: 'Currently you are not in a team, please reach out to your Administrator or Coordinator to proceed with next steps.'
- });
- break;
- }
- // check if assessment is locked by other team members
- if (task.isLocked) {
- this.notificationService.lockTeamAssessmentPopUp(
- {
- name: task.submitter.name,
- image: task.submitter.image
- },
- data => {
- if (data.data) {
- this._navigate(['assessment', 'assessment', this.id, task.contextId, task.id]);
- }
- }
- );
- return;
- }
- this._navigate(['assessment', 'assessment', this.id, task.contextId, task.id]);
- break;
- case 'Topic':
- this._navigate(['topic', this.id, task.id]);
- break;
- case 'Locked':
- this.notificationService.popUp('shortMessage', { message: 'This part of the app is still locked. You can unlock the features by engaging with the app and completing all tasks.' });
- break;
- }
- }
-
- gotoEvent(event?) {
- // go to the event page without choosing any event
- if (!event) {
- return this.router.navigate(['app', 'events', { activity_id: this.id }]);
- }
- // display the event pop up for mobile
- if (this.utils.isMobile()) {
- return this.eventListService.eventDetailPopUp(event);
- }
- // go to the event page with an event selected
- return this.router.navigate(['app', 'events', { activity_id: this.id, event_id: event.id }]);
- }
-
- /**
- * Manually change the status of a task
- * @param type The type of the task('Assessment', 'Topic')
- * @param id The id of the task
- * @param status The status
- */
- changeTaskStatus(type: string, id: number, status: string) {
- const index = this.activity.tasks.findIndex(t => t.id === +id && t.type === type);
- if (index < 0) {
- return;
- }
- this.activity.tasks[index].status = status;
- // update the cache
- this.apolloService.writeFragment({
- id: `Task:${this.activity.tasks[index].type.toLowerCase()}${this.activity.tasks[index].id}`,
- fragment: `
- fragment task on Task {
- status {
- status
- __typename
- }
- __typename
- }
- `,
- data: {
- status: {
- status: status,
- __typename: 'TaskStatus'
- },
- __typename: 'Task'
- }
- });
- }
-
- /******************
- Used for task layout
- ******************/
- taskLeadingIcon(task) {
- switch (task.type) {
- case 'Locked':
- return 'lock-closed-outline';
- case 'Topic':
- return 'reader-outline';
- case 'Assessment':
- return 'clipboard-outline';
- }
- }
-
- assessmentNotSubmitted(task) {
- return task.type === 'Assessment' && (!task.status || task.status === '' || task.status === 'in progress');
- }
-
- taskSubtitle2(task) {
- if (task.type === 'Locked') {
- return '';
- }
- let title = task.type + ' ';
- title += task.isLocked ? '- Locked by ' + task.submitter.name : task.status;
- return title;
- }
-
- taskEndingIcon(task) {
- if (task.isLocked) {
- return 'lock-closed-outline';
- }
- switch (task.status) {
- case 'done':
- return 'checkmark';
- case 'pending review':
- return 'hourglass-outline';
- case 'feedback available':
- case 'in progress':
- default:
- return 'arrow-forward';
- }
- }
-
-}
diff --git a/src/app/activity/activity.module.ts b/src/app/activity/activity.module.ts
deleted file mode 100644
index eecd50570..000000000
--- a/src/app/activity/activity.module.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { NgModule } from '@angular/core';
-import { SharedModule } from '@shared/shared.module';
-import { ActivityRoutingModule } from './activity-routing.module';
-import { ActivityRoutingComponent } from './activity-routing.component';
-import { ActivityComponent } from './activity.component';
-import { ActivityService } from './activity.service';
-
-@NgModule({
- imports: [
- SharedModule,
- ActivityRoutingModule
- ],
- declarations: [
- ActivityRoutingComponent,
- ActivityComponent
- ],
- providers: [
- ActivityService
- ],
- exports: [
- ActivityComponent
- ]
-})
-export class ActivityModule {}
diff --git a/src/app/activity/activity.service.spec.ts b/src/app/activity/activity.service.spec.ts
deleted file mode 100644
index 6728d465e..000000000
--- a/src/app/activity/activity.service.spec.ts
+++ /dev/null
@@ -1,282 +0,0 @@
-import { TestBed, fakeAsync, tick, flushMicrotasks } from '@angular/core/testing';
-import { ActivityService } from './activity.service';
-import { of, throwError } from 'rxjs';
-import { RequestService } from '@shared/request/request.service';
-import { UtilsService } from '@services/utils.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { Router } from '@angular/router';
-import { MockRouter } from '@testing/mocked.service';
-import { TestUtils } from '@testing/utils';
-
-describe('ActivityService', () => {
- let service: ActivityService;
- let requestSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
- let notificationSpy: jasmine.SpyObj;
- let storageSpy: jasmine.SpyObj;
- let utils: UtilsService;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- providers: [
- ActivityService,
- {
- provide: UtilsService,
- useClass: TestUtils,
- },
- {
- provide: RequestService,
- useValue: jasmine.createSpyObj('RequestService', [
- 'get',
- 'post',
- 'graphQLWatch',
- 'graphQLFetch',
- ])
- },
- {
- provide: NotificationService,
- useValue: jasmine.createSpyObj('NotificationService', ['activityCompletePopUp'])
- },
- {
- provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', ['getUser', 'getReferrer'])
- },
- {
- provide: Router,
- useClass: MockRouter,
- },
- ]
- });
- service = TestBed.inject(ActivityService);
- requestSpy = TestBed.inject(RequestService) as jasmine.SpyObj;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- notificationSpy = TestBed.inject(NotificationService) as jasmine.SpyObj;
- utils = TestBed.inject(UtilsService) as jasmine.SpyObj;
- storageSpy = TestBed.inject(BrowserStorageService) as jasmine.SpyObj;
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-
- it('when testing getActivity(), should get the correct data', () => {
- const requestResponse = {
- data: {
- activity: {
- id: 1,
- name: 'activity',
- description: 'des',
- tasks: [
- {
- id: 1,
- type: 'topic',
- name: 'topic 1',
- isLocked: true
- },
- {
- id: 11,
- type: 'topic',
- name: 'topic 2',
- isLocked: false,
- status: {
- status: 'done'
- }
- },
- {
- id: 21,
- type: 'assessment',
- name: 'asmt 21',
- isTeam: false,
- isLocked: false,
- deadline: '2019-02-02',
- contextId: 211,
- status: {
- status: 'in progress',
- isLocked: false,
- submitterName: 'sub name',
- submitterImage: 'sub image'
- }
- },
- {
- id: 22,
- type: 'assessment',
- name: 'asmt 22',
- isTeam: false,
- isLocked: false,
- deadline: '2039-02-02',
- contextId: 211,
- status: {
- status: 'pending approval',
- isLocked: false,
- submitterName: 'sub name',
- submitterImage: 'sub image'
- }
- }
- ]
- }
- }
- };
- const activity = requestResponse.data.activity;
- const topic1 = JSON.parse(JSON.stringify(activity.tasks[0]));
- const topic2 = JSON.parse(JSON.stringify(activity.tasks[1]));
- const assessment1 = JSON.parse(JSON.stringify(activity.tasks[2]));
- const assessment2 = JSON.parse(JSON.stringify(activity.tasks[3]));
- const expected = {
- id: activity.id,
- name: activity.name,
- description: activity.description,
- tasks: [
- {
- id: 0,
- type: 'Locked',
- name: 'Locked'
- },
- {
- id: topic2.id,
- type: 'Topic',
- name: topic2.name,
- status: topic2.status.status
- },
- {
- id: assessment1.id,
- type: 'Assessment',
- name: assessment1.name,
- contextId: 211,
- isForTeam: assessment1.isTeam,
- dueDate: assessment1.deadline,
- isOverdue: true,
- isDueToday: false,
- status: assessment1.status.status,
- isLocked: assessment1.status.isLocked,
- submitter: {
- name: assessment1.status.submitterName,
- image: assessment1.status.submitterImage
- }
- },
- {
- id: assessment2.id,
- type: 'Assessment',
- name: assessment2.name,
- contextId: assessment2.contextId,
- isForTeam: assessment2.isTeam,
- dueDate: assessment2.deadline,
- isOverdue: false,
- isDueToday: false,
- status: 'pending review',
- isLocked: assessment2.status.isLocked,
- submitter: {
- name: assessment2.status.submitterName,
- image: assessment2.status.submitterImage
- }
- }
- ]
- };
- requestSpy.graphQLWatch.and.returnValue(of(requestResponse));
- service.getActivity(1).subscribe(res => expect(res).toEqual(expected));
- });
-
- describe('getNextTask()', () => {
- it('should return in format: { is_last, task }', () => {
- const data = {
- is_last: true,
- task: null
- };
- const expected = {
- isLast: true,
- task: null,
- };
-
- requestSpy.get.and.returnValue(of({
- data
- }));
-
- service.getNextTask(1, '', 1).subscribe(res => {
- expect(res).toEqual(expected);
- }, err => {
- console.log('ERR123::', err);
- expect(err).toBeFalsy();
- });
- });
- });
-
- describe('when testing gotoNextTask()', () => {
- it('should go to home page', fakeAsync(() => {
- requestSpy.get.and.returnValue(of({
- data: {
- is_last: true,
- task: null
- }
- }));
- service.gotoNextTask(1, 'assessment', 2);
- tick();
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['app', 'home']);
- expect(routerSpy.navigate.calls.first().args[1]).toEqual({
- queryParams: {
- activityId: 1,
- activityCompleted: true,
- }
- });
- }));
-
- it('should go to external url', fakeAsync(() => {
- requestSpy.get.and.returnValue(of({
- data: {
- is_last: true,
- task: null
- }
- }));
- storageSpy.getReferrer.and.returnValue({
- activityTaskUrl: 'abc',
- });
- service.gotoNextTask(1, 'assessment', 2);
- tick();
- expect(utils.redirectToUrl).toHaveBeenCalled();
- }));
-
- it('should pop up modal', fakeAsync(() => {
- requestSpy.get.and.returnValue(of({
- data: {
- is_last: true,
- task: {
- id: 11,
- name: 'assessment1',
- type: 'assessment',
- context_id: 12
- }
- }
- }));
- service.gotoNextTask(1, 'assessment', 2);
- tick();
- expect(notificationSpy.activityCompletePopUp.calls.count()).toBe(1);
- }));
-
- it('should go to assessment page', fakeAsync(() => {
- requestSpy.get.and.returnValue(of({
- data: {
- is_last: false,
- task: {
- id: 11,
- name: 'assessment1',
- type: 'assessment',
- context_id: 12
- }
- }
- }));
- service.gotoNextTask(1, 'assessment', 2).then(res => expect(res).toEqual(['assessment', 'assessment', '1', '12', '11']));
- }));
- it('should go to topic page', fakeAsync(() => {
- requestSpy.get.and.returnValue(of({
- data: {
- is_last: false,
- task: {
- id: 11,
- name: 'topic1',
- type: 'topic'
- }
- }
- }));
- service.gotoNextTask(1, 'topic', 2).then(res => expect(res).toEqual(['topic', '1', '11']));
- }));
- });
-});
diff --git a/src/app/activity/activity.service.ts b/src/app/activity/activity.service.ts
deleted file mode 100644
index b00389458..000000000
--- a/src/app/activity/activity.service.ts
+++ /dev/null
@@ -1,209 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Observable, of, forkJoin } from 'rxjs';
-import { map } from 'rxjs/operators';
-import { RequestService } from '@shared/request/request.service';
-import { UtilsService } from '@services/utils.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { Router } from '@angular/router';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-
-/**
- * @name api
- * @description list of api endpoint involved in this service
- * @type {Object}
- */
-const api = {
- nextTask: 'api/v2/plans/activity/next_task'
-};
-
-export interface Activity {
- id: number;
- name: string;
- description?: string;
- tasks: Array;
-}
-
-export interface Task {
- id: number;
- type: string;
- name: string;
- status?: string;
- contextId?: number;
- isForTeam?: boolean;
- dueDate?: string;
- isOverdue?: boolean;
- isDueToday?: boolean;
- isLocked?: boolean;
- submitter?: {
- name: string;
- image: string;
- };
-}
-
-@Injectable({
- providedIn: 'root'
-})
-
-export class ActivityService {
- public tasks: Array;
-
- constructor(
- private request: RequestService,
- private utils: UtilsService,
- public storage: BrowserStorageService,
- private router: Router,
- private readonly newRelic: NewRelicService,
- private notification: NotificationService
- ) {}
-
- public getActivity(id) {
- return this.request.graphQLWatch(
- `query getActivity($id: Int!) {
- activity(id:$id){
- id name description tasks{
- id name type isLocked isTeam deadline contextId status{
- status isLocked submitterName submitterImage
- }
- }
- }
- }`,
- {
- id: id
- }
- ).pipe(map(res => this._normaliseActivity(res.data)));
- }
-
- private _normaliseActivity(data): Activity {
- if (!data) {
- return null;
- }
- // clone the return data, instead of modifying it
- const result = JSON.parse(JSON.stringify(data.activity));
- result.tasks = result.tasks.map(task => {
- if (task.isLocked) {
- return {
- id: 0,
- type: 'Locked',
- name: 'Locked'
- };
- }
- switch (task.type.toLowerCase()) {
- case 'topic':
- return {
- id: task.id,
- name: task.name,
- type: 'Topic',
- status: task.status.status
- };
-
- case 'assessment':
- return {
- id: task.id,
- name: task.name,
- type: 'Assessment',
- contextId: task.contextId,
- isForTeam: task.isTeam,
- dueDate: task.deadline,
- isOverdue: task.deadline ? this.utils.timeComparer(task.deadline) < 0 : false,
- isDueToday: task.deadline ? this.utils.timeComparer(task.deadline, { compareDate: true }) === 0 : false,
- status: task.status.status === 'pending approval' ? 'pending review' : task.status.status,
- isLocked: task.status.isLocked,
- submitter: {
- name: task.status.submitterName,
- image: task.status.submitterImage
- }
- };
- default:
- console.warn(`Unsupported model type ${task.type}`);
- return {
- id: task.id,
- name: task.name,
- type: task.type
- };
- }
- });
- return result;
- }
-
- /**
- * Go to the next task within the same activity, or go back to former layer
- * Logic:
- * - If current task is not the last task in the activity, go to the next task
- * - If current task is the last task in the activity and there's no unfinished task before the current task, go to the home page
- * - If current task is the last task in the activity and there is unfinished task before the current task, show a pop up for user to choose whether go to the activity page or home page
- *
- * @param activityId Activity id
- * @param taskType Current task type ('assessment'/'topic')
- * @param taskId Current task id
- * @param justFinished Whether the current task is just finished or not
- */
- async gotoNextTask(activityId: number, taskType: string, taskId: number, justFinished = true): Promise {
- const res = await this.getNextTask(activityId, taskType, taskId).toPromise();
- // go to next task
- if (!res.isLast) {
- // go to the next task
- let route = ['app', 'home'];
- switch (res.task.type) {
- case 'assessment':
- route = ['assessment', 'assessment', activityId.toString(), res.task.contextId.toString(), res.task.id.toString()];
- break;
-
- case 'topic':
- route = ['topic', activityId.toString(), res.task.id.toString()];
- break;
- }
- return route;
- }
-
- // check if we need to redirect user to external url
- const referrer = this.storage.getReferrer();
- if (this.utils.has(referrer, 'activityTaskUrl')) {
- this.newRelic.actionText('browse to Activity Task return link');
- this.utils.redirectToUrl(referrer.activityTaskUrl);
- return ;
- }
-
- if (res.task) {
- // pop up activity completed modal
- this.notification.activityCompletePopUp(activityId, justFinished);
- return ;
- }
-
- // go back to home page, and scroll to the activity
- if (justFinished) {
- // and display the toast
- this.router.navigate(['app', 'home'], { queryParams: { activityId: activityId, activityCompleted: true } });
- } else {
- // and don't display the toast
- this.router.navigate(['app', 'home'], { queryParams: { activityId: activityId } });
- }
- }
-
- /**
- * Get the data needed to find next task
- * @param activityId The id of current activity
- * @param currentTaskType The type of current task
- * @param currentTaskId The id of current task
- */
- getNextTask(activityId: number, currentTaskType: string, currentTaskId: number): Observable <{ isLast: boolean; task: Task; }> {
- return this.request.get(api.nextTask, {
- params: {
- activity_id: activityId,
- task_type: currentTaskType.toLowerCase(),
- task_id: currentTaskId
- }
- }).pipe(map(res => {
- return {
- isLast: res.data.is_last,
- task: !this.utils.isEmpty(res.data.task) ? {
- id: res.data.task.id,
- name: res.data.task.name,
- type: res.data.task.type,
- contextId: res.data.task.context_id || null
- } : null
- };
- })
- );
- }
-}
diff --git a/src/app/animations.ts b/src/app/animations.ts
deleted file mode 100644
index d1a756396..000000000
--- a/src/app/animations.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import {
- animation, trigger, animateChild, group,
- transition, animate, style, query
-} from '@angular/animations';
-
-export const fadeIn = animation([
- style({ opacity: 0 }),
- animate('{{time}}', style({ opacity: 1 })),
-]);
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
deleted file mode 100644
index 366238c52..000000000
--- a/src/app/app-routing.module.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import { NgModule } from '@angular/core';
-import { Routes, RouterModule } from '@angular/router';
-import { GoMobileComponent } from './go-mobile/go-mobile.component';
-import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
-import { FastFeedbackComponent } from './fast-feedback/fast-feedback.component';
-import { DeviceInfoComponent } from './device-info/device-info.component';
-import { AuthGuard } from './auth/auth.guard';
-import { ProgramSelectedGuard } from './auth/program-selected.guard';
-
-const routes: Routes = [
- {
- path: 'go-mobile',
- component: GoMobileComponent,
- canLoad: [AuthGuard],
- },
- {
- path: 'switcher',
- loadChildren: () => import('./switcher/switcher.module').then(m => m.SwitcherModule),
- canLoad: [AuthGuard]
- },
- {
- path: 'topic',
- loadChildren: () => import('./topic/topic.module').then(m => m.TopicModule),
- canLoad: [AuthGuard]
- },
- {
- path: 'assessment',
- loadChildren: () => import('./assessment/assessment.module').then(m => m.AssessmentModule),
- canLoad: [AuthGuard]
- },
- {
- path: 'achievements',
- loadChildren: () => import('./achievements/achievements.module').then(m => m.AchievementsModule),
- canLoad: [AuthGuard]
- },
- {
- path: 'activity-task',
- loadChildren: () => import('./tasks/tasks.module').then(m => m.TasksModule),
- canLoad: [AuthGuard]
- },
- {
- path: 'settings-embed',
- loadChildren: () => import('./settings-embed/settings-embed.module').then(m => m.SettingsEmbedModule),
- canLoad: [AuthGuard]
- },
- {
- path: 'fast-feedback',
- component: FastFeedbackComponent,
- canLoad: [AuthGuard]
- },
- {
- path: 'chat',
- loadChildren: () => import('./chat/chat.module').then(m => m.ChatModule),
- canLoad: [AuthGuard]
- },
- {
- path: 'device-info',
- component: DeviceInfoComponent,
- },
- {
- path: '',
- loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsModule),
- canLoad: [AuthGuard],
- canActivate: [ProgramSelectedGuard],
- },
- {
- path: '',
- redirectTo: '/app',
- pathMatch: 'full',
- canLoad: [AuthGuard]
- },
- { path: '**', component: PageNotFoundComponent },
-];
-@NgModule({
- imports: [
- RouterModule.forRoot(routes, {
- enableTracing: false, // <-- debugging purposes only
- scrollPositionRestoration: 'enabled',
- anchorScrolling: 'enabled',
- }),
- ],
- exports: [RouterModule]
-})
-export class AppRoutingModule { }
diff --git a/src/app/app.component.html b/src/app/app.component.html
deleted file mode 100644
index fc9d645fd..000000000
--- a/src/app/app.component.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Skip to main content
- Skip to navigation
-
-
-
-
-
-
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts
deleted file mode 100644
index 8735b787b..000000000
--- a/src/app/app.component.spec.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import { CUSTOM_ELEMENTS_SCHEMA, NgZone } from '@angular/core';
-import { TestBed, async, ComponentFixture } from '@angular/core/testing';
-import {
- HttpTestingController,
- HttpClientTestingModule
-} from '@angular/common/http/testing';
-import { Router } from '@angular/router';
-import { MockRouter } from '@testing/mocked.service';
-import { UtilsService } from '@services/utils.service';
-import { TestUtils } from '@testing/utils';
-import { SharedService } from '@services/shared.service';
-import { AuthService } from './auth/auth.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { VersionCheckService } from '@services/version-check.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { DomSanitizer } from '@angular/platform-browser';
-import { Platform } from '@ionic/angular';
-import { of } from 'rxjs';
-// import { SplashScreen } from '@ionic-native/splash-screen/ngx';
-// import { StatusBar } from '@ionic-native/status-bar/ngx';
-
-import { AppComponent } from './app.component';
-
-describe('AppComponent', () => {
-
- let app: AppComponent;
- let fixture: ComponentFixture;
- let authServiceSpy: jasmine.SpyObj;
- let storage: BrowserStorageService;
- let sharedServiceSpy: SharedService;
- let utilsSpy: UtilsService;
- let routerSpy: Router;
- let /* statusBarSpy, splashScreenSpy, platformReadySpy, */ platformSpy;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- imports: [HttpClientTestingModule],
- declarations: [AppComponent],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
- providers: [
- {
- provide: Router,
- useClass: MockRouter,
- },
- {
- provide: UtilsService,
- useValue: jasmine.createSpyObj('UtilsService', ['getQueryParams', 'urlQueryToObject', 'isEmpty', 'changeThemeColor', 'has']),
- },
- {
- provide: SharedService,
- useValue: jasmine.createSpyObj('SharedService', ['onPageLoad', 'initWebServices']),
- },
- {
- provide: AuthService,
- useValue: jasmine.createSpyObj('AuthService', {
- 'getConfig': of({}),
- 'isAuthenticated': of({}),
- 'getStackConfig': of(true),
- })
- },
- {
- provide: Platform,
- useValue: jasmine.createSpyObj('Platform', {
- 'ready': new Promise((resolve) => resolve(true))
- }),
- },
- {
- provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', [
- 'get',
- 'set',
- 'setConfig',
- 'getUser',
- 'stackConfig',
- ])
- },
- {
- provide: VersionCheckService,
- useValue: jasmine.createSpyObj('VersionCheckService', ['initiateVersionCheck'])
- },
- {
- provide: NewRelicService,
- useValue: jasmine.createSpyObj('NewRelicService', ['noticeError']),
- },
- DomSanitizer,
- ],
- }).compileComponents();
-
- fixture = TestBed.createComponent(AppComponent);
- app = fixture.debugElement.componentInstance;
- platformSpy = TestBed.inject(Platform);
- authServiceSpy = TestBed.inject(AuthService) as jasmine.SpyObj;
- storage = TestBed.inject(BrowserStorageService) as jasmine.SpyObj;
- sharedServiceSpy = TestBed.inject(SharedService) as jasmine.SpyObj;
- utilsSpy = TestBed.inject(UtilsService) as jasmine.SpyObj;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
-
- }));
-
- it('should create the app', () => {
- expect(app).toBeTruthy();
- });
-
- it('should initialize the app', async () => {
- TestBed.createComponent(AppComponent);
- expect(platformSpy.ready).toHaveBeenCalled();
- expect(sharedServiceSpy.initWebServices).toHaveBeenCalled();
- });
-
- // TODO: add more tests!
-
-});
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
deleted file mode 100644
index 58644b941..000000000
--- a/src/app/app.component.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import { Component, OnInit, NgZone } from '@angular/core';
-import { Router } from '@angular/router';
-import { Platform } from '@ionic/angular';
-import { UtilsService } from '@services/utils.service';
-import { SharedService } from '@services/shared.service';
-import { AuthService } from './auth/auth.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { VersionCheckService } from '@services/version-check.service';
-import { environment } from '@environments/environment';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { DomSanitizer } from '@angular/platform-browser';
-
-@Component({
- selector: 'app-root',
- templateUrl: 'app.component.html'
-})
-export class AppComponent implements OnInit {
- customHeader: string | any;
- constructor(
- private platform: Platform,
- private router: Router,
- public utils: UtilsService,
- private sharedService: SharedService,
- private authService: AuthService,
- private storage: BrowserStorageService,
- private versionCheckService: VersionCheckService,
- private ngZone: NgZone,
- private newRelic: NewRelicService,
- public sanitizer: DomSanitizer,
- private cleanupService: ComponentCleanupService
- ) {
- this.customHeader = null;
- this.initializeApp();
- }
-
- // force every navigation happen under radar of angular
- private navigate(direction): Promise {
- return this.ngZone.run(() => {
- return this.router.navigate(direction);
- });
- }
-
- private configVerification(): void {
- if (this.storage.get('fastFeedbackOpening')) { // set default modal status
- this.storage.set('fastFeedbackOpening', false);
- }
- }
-
- ngOnInit() {
- this.configVerification();
- this.sharedService.onPageLoad();
-
- // @TODO: need to build a new micro service to get the config and serve the custom branding config from a microservice
- // Get the custom branding info and update the theme color if needed
- const domain = window.location.hostname;
- this.authService.getConfig({domain}).subscribe(
- (response: any) => {
- if (response !== null) {
- const expConfig = response.data;
- const numOfConfigs = expConfig.length;
- if (numOfConfigs > 0 && numOfConfigs < 2) {
- let logo = expConfig[0].logo;
-
- const config = expConfig[0].config || {}; // let it fail gracefully
-
- if (config.html_branding && config.html_branding.header) {
- this.customHeader = config.html_branding.header;
- }
- if (this.customHeader) {
- this.customHeader = this.sanitizer.bypassSecurityTrustHtml(this.customHeader);
- }
-
- // add the domain if the logo url is not a full url
- if (!this.utils.isEmpty(logo) && !logo?.includes('http')) {
- logo = environment.APIEndpoint + logo;
- }
- const colors = {
- theme: config.theme_color,
- };
- this.storage.setConfig({
- logo,
- colors,
- });
-
- // use brand color from getConfig API if no cached color available
- // in storage.getUser()
- if (!this.utils.has(this.storage.getUser(), 'colors') || !this.storage.getUser().colors) {
- this.utils.changeThemeColor(colors);
- }
- }
- }
- },
- err => {
- this.newRelic.noticeError(`${JSON.stringify(err)}`);
- }
- );
-
- let searchParams = null;
- let queryString = '';
- if (window.location.search) {
- queryString = window.location.search.substring(1);
- } else if (window.location.hash) {
- queryString = window.location.hash.substring(2);
- }
- searchParams = new URLSearchParams(queryString);
-
- if (searchParams.has('apikey')) {
- const queries = this.utils.urlQueryToObject(queryString);
- return this.navigate(['global_login', searchParams.get('apikey'), queries]);
- }
-
- if (searchParams.has('do')) {
- this.authService.deeplink = window.location.href;
- switch (searchParams.get('do')) {
- case 'secure':
- if (searchParams.has('auth_token')) {
- const queries = this.utils.urlQueryToObject(queryString);
- this.navigate([
- 'secure',
- searchParams.get('auth_token'),
- queries
- ]);
- }
- break;
- case 'resetpassword':
- if (searchParams.has('key') && searchParams.has('email')) {
- this.navigate([
- 'reset_password',
- searchParams.get('key'),
- searchParams.get('email')
- ]);
- }
- break;
-
- case 'registration':
- if (searchParams.has('key') && searchParams.has('email')) {
- this.navigate([
- 'registration',
- searchParams.get('email'),
- searchParams.get('key')
- ]);
- }
- break;
- }
- }
- }
-
- initializeApp() {
- this.platform.ready().then(async () => {
- if (environment.production) {
- // watch version update
- this.versionCheckService.initiateVersionCheck();
- }
- // initialise Pusher when app loading
- this.sharedService.initWebServices();
- });
- }
-
- /**
- * checking conditions to show custom header
- * @param type header
- */
- checkCustom(type: string): boolean {
- if (type === 'header' && this.customHeader && this.authService.isAuthenticated()) {
- return true;
- }
- return false;
- }
-
-}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
deleted file mode 100644
index fe88fc88a..000000000
--- a/src/app/app.module.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { NgModule } from '@angular/core';
-import { BrowserModule } from '@angular/platform-browser';
-import { RouteReuseStrategy } from '@angular/router';
-import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
-import { HttpClientModule } from '@angular/common/http';
-
-import { AppRoutingModule } from './app-routing.module';
-import { RequestModule } from '@shared/request/request.module';
-import { NewRelicModule } from '@shared/new-relic/new-relic.module';
-import { NotificationModule } from '@shared/notification/notification.module';
-import { AuthModule } from './auth/auth.module';
-import { FastFeedbackModule } from './fast-feedback/fast-feedback.module';
-import { ReviewRatingModule } from './review-rating/review-rating.module';
-import { EventDetailModule } from './event-detail/event-detail.module';
-import { GoMobileModule } from './go-mobile/go-mobile.module';
-
-import { AppComponent } from './app.component';
-import { UtilsService } from './services/utils.service';
-import { VersionCheckService } from './services/version-check.service';
-import { environment } from '@environments/environment';
-import { IntercomModule } from 'ng-intercom';
-import { PusherModule } from '@shared/pusher/pusher.module';
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { UnlockingComponent } from '@components/unlocking/unlocking.component';
-import { DeviceInfoComponent } from './device-info/device-info.component';
-import { ApolloModule } from './shared/apollo/apollo.module';
-import { EmbedVideoModule } from './shared/ngx-embed-video/ngx-embed-video.module';
-import { PageNotFoundModule } from './page-not-found/page-not-found.module';
-
-
-@NgModule({
- declarations: [
- AppComponent,
- UnlockingComponent,
- DeviceInfoComponent,
- ],
- imports: [
- RequestModule.forRoot({
- appkey: environment.appkey,
- prefixUrl: environment.APIEndpoint,
- }),
- ApolloModule,
- AuthModule,
- BrowserModule,
- BrowserAnimationsModule,
- HttpClientModule,
- IonicModule.forRoot(),
- AppRoutingModule,
- NewRelicModule.forRoot(),
- EmbedVideoModule.forRoot(),
- NotificationModule,
- FastFeedbackModule,
- GoMobileModule,
- ReviewRatingModule,
- EventDetailModule,
- PageNotFoundModule,
- PusherModule.forRoot({
- apiurl: environment.APIEndpoint,
- pusherKey: environment.pusherKey,
- }),
- IntercomModule.forRoot({
- appId: environment.intercomAppId,
- updateOnRouterChange: true,
- // will automatically run `update` on router event changes. Default: `false`
- })
- ],
- providers: [
- { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
- // Custom
- UtilsService,
- VersionCheckService,
- ],
- bootstrap: [AppComponent],
-})
-export class AppModule {}
diff --git a/src/app/assessment/assessment-routing.module.ts b/src/app/assessment/assessment-routing.module.ts
deleted file mode 100644
index aa2076de3..000000000
--- a/src/app/assessment/assessment-routing.module.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
-import { AssessmentComponent } from './assessment.component';
-import { SinglePageDeactivateGuard } from '../single-page-deactivate.guard';
-
-const routes: Routes = [
- {
- path: 'assessment/:activityId/:contextId/:id',
- component: AssessmentComponent,
- data: {
- action: 'assessment'
- },
- canDeactivate: [SinglePageDeactivateGuard],
- },
- {
- path: 'assessment/:activityId/:contextId/:id/:submissionId',
- component: AssessmentComponent,
- data: {
- action: 'assessment'
- },
- canDeactivate: [SinglePageDeactivateGuard],
- },
- {
- path: 'review/:contextId/:id/:submissionId',
- component: AssessmentComponent,
- data: {
- action: 'review'
- },
- canDeactivate: [SinglePageDeactivateGuard],
- },
- {
- path: 'event/:contextId/:id',
- component: AssessmentComponent,
- data: {
- action: 'assessment',
- from: 'events'
- },
- }
-];
-
-@NgModule({
- imports: [RouterModule.forChild(routes)],
- exports: [RouterModule]
-})
-export class AssessmentRoutingModule {}
diff --git a/src/app/assessment/assessment.component.html b/src/app/assessment/assessment.component.html
deleted file mode 100644
index 331b319a1..000000000
--- a/src/app/assessment/assessment.component.html
+++ /dev/null
@@ -1,230 +0,0 @@
-
-
-
-
-
-
-
-
- {{pageTitle}}
-
- {{savingMessage}}
- Save
-
-
-
-
- {{ assessment.name }}
-
-
-
-
- {{ this.utils.dueDateFormatter(assessment.dueDate) }}
-
-
-
- Submitted by {{ submission.submitterName }}
-
-
-
-
- Reviewed by {{ submission.reviewerName }}
-
-
-
-
-
-
-
-
-
- Locked by {{ submission.submitterName }}
- Please wait until the user finishes editing
-
-
-
-
-
-
-
-
-
-
-
Currently you are not in a team, please reach out to your Administrator or Coordinator to proceed with next steps.
-
diff --git a/src/app/assessment/assessment.component.scss b/src/app/assessment/assessment.component.scss
deleted file mode 100644
index 6b92a3685..000000000
--- a/src/app/assessment/assessment.component.scss
+++ /dev/null
@@ -1,95 +0,0 @@
-.text-right {
- text-align: right;
-}
-.icon-info {
- font-size: 20px;
-}
-.icon-backward {
- font-size: 20px !important;
-}
-
-.review-submitter {
- p {
- margin-top: 16px;
- margin-bottom: 16px;
- }
-}
-
-.sub-title {
- &.title-ios {
- margin-top: 30px;
- height: 16px;
- }
-}
-.text-button {
- text-transform: capitalize;
-}
-
-ion-header ion-toolbar {
- min-height: 56px;
- .saving-msg {
- margin-top: 30px;
- margin-left: 60px;
- }
-}
-
-.member-detail-container {
- margin-bottom: 0px;
- margin-top: 15px;
- ion-item {
- height: 64px;
- --background: transparent;
- --padding-start: 0px;
- }
- ion-avatar {
- height: 48px;
- width: 48px;
- }
- ion-label {
- h4 {
- font-size: 16px;
- }
- p {
- font-size: 12px;
- color: var(--ion-color-medium-shade);
- }
- }
-}
-ion-footer {
- .footer-text {
- margin: 20px;
- text-transform: capitalize;
- }
- .footer-action {
- height: 40px;
- margin: 10px 20px;
- }
-}
-
-.audience-mentor-question-card {
- border: 2px solid var(--practera-review-border-color);
- ion-card-subtitle {
- text-transform: none !important;
- }
- ion-card-content {
- margin-top: 0 !important;
- padding-top: 16px !important;
- }
-}
-.question-card {
- ion-card-header {
- padding-bottom: 14px;
- border-bottom: 1px solid var(--ion-color-light-shade);
- }
-}
-
-.not-team {
- display: flex;
- align-items: center;
- justify-content: center;
- align-content: center;
- flex-wrap: nowrap;
- flex-direction: row;
- margin: auto;
- height: 100%;
-}
diff --git a/src/app/assessment/assessment.component.spec.ts b/src/app/assessment/assessment.component.spec.ts
deleted file mode 100644
index f732a93c4..000000000
--- a/src/app/assessment/assessment.component.spec.ts
+++ /dev/null
@@ -1,754 +0,0 @@
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
-import { async, ComponentFixture, TestBed, fakeAsync, tick, inject, flushMicrotasks, flush } from '@angular/core/testing';
-import { QuestionsModule } from '@app/questions/questions.module';
-
-import { Router, ActivatedRoute, convertToParamMap } from '@angular/router';
-import { AssessmentComponent } from './assessment.component';
-import { AssessmentService } from './assessment.service';
-import { UtilsService } from '@services/utils.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { ActivityService } from '@app/activity/activity.service';
-import { FastFeedbackService } from '@app/fast-feedback/fast-feedback.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { SharedService } from '@services/shared.service';
-import { FastFeedbackServiceMock } from '@testing/mocked.service';
-import { of } from 'rxjs';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { MockRouter, MockNewRelicService } from '@testing/mocked.service';
-import { TestUtils } from '@testing/utils';
-
-class Page {
- get savingMessage() {
- return this.query('ion-title.sub-title');
- }
- get assessmentName() {
- return this.query('h1');
- }
- get assessmentDescription() {
- return this.query('ion-content app-description');
- }
- get overDueMsg() {
- return this.query('p.over');
- }
- get dueMsg() {
- return this.query('p.due-date');
- }
- get submitterMsg() {
- return this.query('.review-submitter .title');
- }
- get lockedImg() {
- return this.query('ion-list.member-detail-container ion-avatar img');
- }
- get lockedTitle() {
- return this.query('ion-list.member-detail-container ion-label h4');
- }
- get groupNames() {
- return this.queryAll('form h3');
- }
- get groupDescriptions() {
- return this.queryAll('.g-description');
- }
- get questionNames() {
- return this.queryAll('.q-title');
- }
- get questionRequiredIndicators() {
- return this.queryAll('.required-indicator');
- }
- get questionInfos() {
- return this.queryAll('.icon-info');
- }
- get questionDescriptions() {
- return this.queryAll('.q-description');
- }
- get questionContent() {
- return this.queryAll('.q-content');
- }
- get noAnswerMsg() {
- return this.queryAll('.q-content p');
- }
- get submitBtn() {
- return this.query('#btn-submit');
- }
-
- fixture: ComponentFixture;
-
- constructor(fixture: ComponentFixture) {
- this.fixture = fixture;
- }
-
- //// query helpers ////
- private query(selector: string): T {
- return this.fixture.nativeElement.querySelector(selector);
- }
-
- private queryAll(selector: string): T[] {
- return this.fixture.nativeElement.querySelectorAll(selector);
- }
-}
-
-describe('AssessmentComponent', () => {
- let component: AssessmentComponent;
- let fixture: ComponentFixture;
- let page: Page;
- let assessmentSpy: jasmine.SpyObj;
- let notificationSpy: jasmine.SpyObj;
- let activitySpy: jasmine.SpyObj;
- let fastFeedbackSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
- let routeStub: Partial;
- let storageSpy: jasmine.SpyObj;
- let shared: SharedService;
- let utils: UtilsService;
-
- const mockQuestions = [
- {
- id: 123,
- name: 'test',
- description: 'test',
- canAnswer: true,
- canComment: false,
- type: 'text',
- isRequired: true,
- audience: ['participant', 'mentor', 'submitter', 'reviewer']
- },
- {
- id: 124,
- name: 'test',
- description: 'test',
- canAnswer: true,
- canComment: false,
- type: 'text',
- isRequired: false,
- audience: ['participant', 'mentor', 'submitter', 'reviewer']
- },
- {
- id: 125,
- name: 'test',
- description: 'test',
- canAnswer: true,
- canComment: false,
- type: 'multiple',
- isRequired: false,
- audience: ['participant', 'mentor', 'submitter', 'reviewer']
- }
- ];
-
- const mockAssessment = {
- name: 'test',
- description: 'test',
- type: 'quiz',
- isForTeam: false,
- dueDate: '2029-02-02',
- isOverdue: false,
- pulseCheck: false,
- groups: [{
- name: 'test groups',
- description: 'test groups description',
- questions: mockQuestions,
- }],
- };
- const mockSubmission = {
- id: 1,
- status: 'in progress',
- answers: [],
- submitterName: 'name',
- modified: '2019-02-02',
- completed: false,
- isLocked: false,
- submitterImage: '',
- reviewerName: 'name'
- };
- const mockReview = {
- id: 1,
- answers: {},
- status: 'in progress',
- modified: '2019-02-02'
- };
- const mockUser = {
- role: 'participant',
- teamId: 1,
- projectId: 2,
- name: 'Test User',
- email: 'user@test.com',
- id: 1
- };
-
- beforeEach(async () => {
- TestBed.configureTestingModule({
- imports: [ReactiveFormsModule, QuestionsModule, HttpClientTestingModule],
- declarations: [AssessmentComponent],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
- providers: [
- {
- provide: ActivatedRoute,
- useValue: {
- snapshot: {
- paramMap: convertToParamMap({
- id: 1,
- activityId: 2,
- contextId: 3,
- submissionId: 4
- }),
- data: {
- action: 'assessment',
- from: ''
- },
- },
- params: of(true),
- }
- },
- {
- provide: UtilsService,
- useClass: TestUtils,
- },
- {
- provide: SharedService,
- useValue: jasmine.createSpyObj('SharedService', ['stopPlayingVideos'])
- },
- {
- provide: NewRelicService,
- useClass: MockNewRelicService,
- },
- {
- provide: AssessmentService,
- useValue: jasmine.createSpyObj('AssessmentService', ['getAssessment', 'saveAnswers', 'saveFeedbackReviewed', 'popUpReviewRating'])
- },
- {
- provide: NotificationService,
- useValue: jasmine.createSpyObj('NotificationService', ['alert', 'customToast', 'popUp', 'presentToast', 'modalOnly'])
- },
- {
- provide: ActivityService,
- useValue: jasmine.createSpyObj('ActivityService', ['gotoNextTask'])
- },
- {
- provide: FastFeedbackService,
- useClass: FastFeedbackServiceMock
- },
- {
- provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', ['getUser', 'getReferrer', 'get'])
- },
- {
- provide: Router,
- useClass: MockRouter,
- },
- ]
- }).compileComponents();
-
- });
-
- beforeEach(async () => {
- fixture = TestBed.createComponent(AssessmentComponent);
- component = fixture.componentInstance;
-
- page = new Page(fixture);
- assessmentSpy = TestBed.inject(AssessmentService) as jasmine.SpyObj;
- notificationSpy = TestBed.inject(NotificationService) as jasmine.SpyObj;
- activitySpy = TestBed.inject(ActivityService) as jasmine.SpyObj;
- fastFeedbackSpy = TestBed.inject(FastFeedbackService) as jasmine.SpyObj;
- routeStub = TestBed.inject(ActivatedRoute);
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- storageSpy = TestBed.inject(BrowserStorageService) as jasmine.SpyObj;
- shared = TestBed.inject(SharedService);
- utils = TestBed.inject(UtilsService);
-
- // initialise service calls
- assessmentSpy.getAssessment.and.returnValue(of({
- assessment: mockAssessment,
- submission: null,
- review: null
- }));
- assessmentSpy.saveAnswers.and.returnValue(of(true));
- assessmentSpy.saveFeedbackReviewed.and.returnValue(of({ success: true }));
- activitySpy.gotoNextTask.and.returnValue(new Promise(() => { }));
- storageSpy.getUser.and.returnValue(mockUser);
- component.routeUrl = '/test';
- });
-
- it('should be created', () => {
- expect(component).toBeTruthy();
- });
-
- it('should get correct parameters from routing', () => {
- fixture.detectChanges();
- expect(component.id).toEqual(1);
- expect(component.activityId).toEqual(2);
- expect(component.contextId).toEqual(3);
- expect(component.submissionId).toEqual(4);
- expect(component.action).toEqual('assessment');
- });
-
- describe('when testing getAssessment()', () => {
- let tmpAssessment, tmpSubmission, tmpReview, customTests;
- beforeEach(() => {
- tmpAssessment = mockAssessment;
- tmpSubmission = null;
- tmpReview = null;
- customTests = () => { };
- });
- afterEach(() => {
- assessmentSpy.getAssessment.and.returnValue(of({
- assessment: tmpAssessment,
- submission: tmpSubmission,
- review: tmpReview
- }));
- fixture.detectChanges();
- customTests();
- });
- it('should get correct assessment and display correct info in html', () => {
- customTests = () => {
- expect(component.assessment).toEqual(mockAssessment);
- expect(component.loadingAssessment).toEqual(false);
- expect(page.savingMessage).toBeFalsy();
- expect(page.assessmentName.innerHTML).toEqual(mockAssessment.name);
- expect(page.assessmentDescription).toBeTruthy();
- expect(page.overDueMsg).toBeFalsy();
- expect(page.dueMsg.innerHTML.trim()).toEqual(utils.dueDateFormatter(mockAssessment.dueDate));
- mockAssessment.groups.forEach((group, groupIndex) => {
- expect(page.groupNames[groupIndex].innerHTML).toEqual(group.name);
- expect(page.groupDescriptions[groupIndex]).toBeTruthy();
- group.questions.forEach((question, questionIndex) => {
- expect(page.questionNames[questionIndex].innerHTML).toContain(question.name);
- expect(page.questionDescriptions[questionIndex]).toBeTruthy();
- });
- });
- expect(notificationSpy.alert.calls.count()).toBe(0);
- };
- });
-
- it('should pop up alert if it is team assessment and user is not in team', () => {
- tmpAssessment = JSON.parse(JSON.stringify(mockAssessment));
- tmpAssessment.isForTeam = true;
- const tmpUser = JSON.parse(JSON.stringify(mockUser));
- tmpUser.teamId = null;
- storageSpy.getUser.and.returnValue(tmpUser);
- customTests = () => {
- expect(notificationSpy.alert.calls.count()).toBe(1);
- };
- });
-
- it('should get correct in progress submission', () => {
- tmpSubmission = mockSubmission;
- customTests = () => {
- expect(component.submission).toEqual(mockSubmission);
- expect(component.doAssessment).toBe(true);
- expect(component.doReview).toBe(false);
- expect(component.savingMessage).toEqual('Last saved ' + utils.timeFormatter(mockSubmission.modified));
- expect(component.savingButtonDisabled).toBe(false);
- };
- });
-
- it('should get correct in progress locked submission', () => {
- tmpSubmission = JSON.parse(JSON.stringify(mockSubmission));
- tmpSubmission.isLocked = true;
- customTests = () => {
- expect(component.doAssessment).toBe(false);
- expect(component.doReview).toBe(false);
- expect(component.savingButtonDisabled).toBe(true);
- expect(component.submission.status).toEqual('done');
- };
- });
-
- it('should get correct done submission', () => {
- tmpSubmission = JSON.parse(JSON.stringify(mockSubmission));
- tmpSubmission.status = 'done';
- customTests = () => {
- expect(component.submission).toEqual(tmpSubmission);
- expect(component.doAssessment).toBe(false);
- expect(component.doReview).toBe(false);
- expect(component.savingButtonDisabled).toBe(false);
- };
- });
-
- it('should get correct in progress review', () => {
- tmpAssessment.type = 'moderated';
- tmpSubmission = JSON.parse(JSON.stringify(mockSubmission));
- tmpSubmission.status = 'pending review';
- tmpReview = mockReview;
- routeStub.snapshot.data.action = 'review';
- customTests = () => {
- expect(component.review).toEqual(mockReview);
- expect(component.doAssessment).toBe(false);
- expect(component.doReview).toBe(true);
- expect(component.savingButtonDisabled).toBe(false);
- };
- });
-
- it('should get correct published review', () => {
- tmpSubmission = JSON.parse(JSON.stringify(mockSubmission));
- tmpSubmission.status = 'published';
- tmpReview = JSON.parse(JSON.stringify(mockReview));
- tmpReview.status = '';
- customTests = () => {
- expect(component.review).toEqual(tmpReview);
- expect(component.doAssessment).toBe(false, 'not do assessment');
- expect(component.doReview).toBe(false, 'not do review');
- expect(component.savingButtonDisabled).toBe(false);
- expect(component.feedbackReviewed).toBe(false);
- };
- });
-
- it('should get correct review published status', () => {
- tmpSubmission = JSON.parse(JSON.stringify(mockSubmission));
- tmpSubmission.status = 'published';
- tmpSubmission.completed = true;
- tmpReview = JSON.parse(JSON.stringify(mockReview));
- tmpReview.status = 'done';
- customTests = () => {
- expect(component.feedbackReviewed).toBe(true);
- };
- });
- });
-
- it('should navigate to the correct page #1', () => {
- spyOn(component.navigate, 'emit');
- component.fromPage = 'reviews';
- component.navigateBack();
-
- // let's assume test is run under desktop environment
- expect(component.navigate.emit).toHaveBeenCalled();
- });
-
- it('should navigate to the correct page #2', () => {
- component.fromPage = 'events';
- component.navigateBack();
- });
-
- it('should navigate to the correct page #3', () => {
- component.activityId = 1;
- component.navigateBack();
- expect(component.activityId).toEqual(1);
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['app', 'activity', 1]);
- });
-
- it('should navigate to the correct page #4', () => {
- component.activityId = null;
- component.navigateBack();
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['app', 'home']);
- });
-
- it('should pop up mark feedback as read confirmation when going back', () => {
- component.action = 'assessment';
- component.submission.status = 'published';
- component.feedbackReviewed = false;
- component.back();
- expect(routerSpy.navigate.calls.count()).toBe(0);
- });
-
- it('should save in progress and navigate to other page when going back', fakeAsync(() => {
- component.back();
- flush();
- expect(component.savingMessage).toContain('Last saved');
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['app', 'home']);
- }));
-
- it('should list unanswered required questions from compulsoryQuestionsAnswered()', () => {
- expect(component.compulsoryQuestionsAnswered).toBeDefined();
- component.assessment = mockAssessment;
- const answers = [
- {
- 'questionId': 123,
- 'answer': null
- },
- {
- 'questionId': 124,
- 'answer': null
- }
- ];
-
- const unansweredQuestions = component.compulsoryQuestionsAnswered(answers);
- expect(unansweredQuestions).toEqual([mockQuestions[0]]);
- });
-
- it('should return empty from compulsoryQuestionsAnswered() if all required question has been answered', () => {
- expect(component.compulsoryQuestionsAnswered).toBeDefined();
- component.assessment = mockAssessment;
- const answers = [
- {
- 'questionId': 123,
- 'answer': 'abc'
- },
- {
- 'questionId': 124,
- 'answer': null
- }
- ];
- expect(component.compulsoryQuestionsAnswered(answers)).toEqual([]);
- });
-
- describe('should get correct assessment answers when', () => {
- let assessment;
- let answers;
- let savingButtonDisabled = false;
-
- beforeEach(() => {
- component.assessment = mockAssessment;
- component.id = 1;
- component.doAssessment = true;
- component.contextId = 2;
- component.assessment.isForTeam = true;
- component.questionsForm = new FormGroup({
- 'q-123': new FormControl('abc'),
- 'q-124': new FormControl(null),
- 'q-125': new FormControl(null)
- });
- });
-
- afterEach(() => {
- expect(component.savingButtonDisabled).toBe(savingButtonDisabled);
- expect(notificationSpy.popUp.calls.count()).toBe(0);
- expect(assessment.id).toBe(1);
- expect(assessment.contextId).toBe(2);
- expect(answers).toEqual([
- {
- questionId: 123,
- answer: 'abc'
- },
- {
- questionId: 124,
- answer: null
- },
- {
- questionId: 125,
- answer: []
- }
- ]);
- });
-
- it('saving in progress', () => {
- component.submit(true);
- assessment = assessmentSpy.saveAnswers.calls.first().args[0];
- answers = assessmentSpy.saveAnswers.calls.first().args[1];
- expect(component.submitting).toBeFalsy();
- expect(component.savingMessage).toContain('Last saved');
- expect(assessment.inProgress).toBe(true);
- expect(assessment.unlock).toBeFalsy();
- });
-
- it('saving in progress, and unlock the submission for team assessment', () => {
- component.submit(true, true);
- assessment = assessmentSpy.saveAnswers.calls.first().args[0];
- answers = assessmentSpy.saveAnswers.calls.first().args[1];
- expect(assessment.unlock).toBe(true);
- });
-
- it('submitting', () => {
- savingButtonDisabled = true;
- component.submit(false);
- assessment = assessmentSpy.saveAnswers.calls.first().args[0];
- answers = assessmentSpy.saveAnswers.calls.first().args[1];
- expect(component.submitting).toEqual(false);
- expect(component.saving).toBe(true);
- });
- });
-
- it('should pop up alert if required answer missing when submitting', () => {
- component.doAssessment = true;
- fixture.detectChanges();
- component.questionsForm = new FormGroup({
- 'q-123': new FormControl(null),
- 'q-124': new FormControl(null),
- 'q-125': new FormControl(null)
- });
- component.submit(false);
- expect(component.submitting).toBe(false);
- expect(notificationSpy.popUp.calls.count()).toBe(1);
- });
-
- describe('submitting assessment submit(false)', () => {
- const activityId = 1;
- const emptyAnswers = [];
- const action = 'assessment';
- const assessmentId = 0;
-
- beforeEach(() => {
- component.id = activityId;
- component.doAssessment = true;
- component.contextId = 2;
- component.action = action;
- component.assessment = {
- name: 'Test Assessment',
- type: 'quiz',
- description: 'Test Description',
- isForTeam: false,
- dueDate: '',
- isOverdue: false,
- groups: [],
- pulseCheck: true,
- };
- });
-
- it('should be called with correct assessment answer/action/activity status', () => {
- component.submit(false);
- expect(assessmentSpy.saveAnswers).toHaveBeenCalled();
- expect(assessmentSpy.saveAnswers).toHaveBeenCalledWith(
- {
- id: activityId,
- contextId: 2
- },
- emptyAnswers,
- action
- );
- });
-
- it(`should check fastfeedback availability as pulseCheck is 'true'`, () => {
- component.submit(false);
- const spy = spyOn(fastFeedbackSpy, 'pullFastFeedback').and.returnValue(of(fastFeedbackSpy.pullFastFeedback()));
- spyOn(component, 'goToNextTask');
- spyOn(component, 'navigateBack');
- fixture.detectChanges();
- expect(fastFeedbackSpy.pullFastFeedback.calls.count()).toEqual(1);
- if (component.doReview === true) {
- expect(component.navigateBack).toHaveBeenCalled();
- }
- });
-
- it('should skip fastfeedback if pulsecheck = false', () => {
- component.assessment.pulseCheck = false;
- spyOn(fastFeedbackSpy, 'pullFastFeedback');
- spyOn(component, 'goToNextTask');
- component.submit(false);
- expect(fastFeedbackSpy.pullFastFeedback.calls.count()).toEqual(0);
- });
- });
-
- describe('click continue button', () => {
- it('should go to next task', () => {
- component.clickBtnContinue();
- expect(activitySpy.gotoNextTask.calls.count()).toEqual(1);
- expect(component.continueBtnLoading).toBe(true);
- });
- it('should mark feedback as read and go to next task', fakeAsync(() => {
- component.submission.status = 'published';
- component.feedbackReviewed = false;
- component.clickBtnContinue();
- tick();
- expect(assessmentSpy.saveFeedbackReviewed.calls.count()).toEqual(1);
- expect(activitySpy.gotoNextTask.calls.count()).toEqual(1);
- expect(component.continueBtnLoading).toBe(true);
- }));
- it('should go to events page', () => {
- spyOn(component.navigate, 'emit');
- component.fromPage = 'events';
- component.action = 'assessment';
- component.clickBtnContinue();
- expect(component.navigate.emit).toHaveBeenCalled();
- });
- });
-
- describe('when testing footerText()', () => {
- it('should return submitting', () => {
- component.doAssessment = true;
- component.submitting = true;
- expect(component.footerText()).toEqual('submitting');
- });
- it('should return "pending review" (doReview = false & doAssessment = true)', () => {
- component.assessment.type = 'moderated';
- component.doAssessment = true;
- component.doReview = false;
- component.submitted = true;
- expect(component.footerText()).toEqual('pending review');
- });
- it('should return "pending review" (both doReview & doAssessment = true)', () => {
- component.assessment.type = 'moderated';
- component.doAssessment = true;
- component.doReview = true;
- component.submitted = true;
- expect(component.footerText()).toEqual('pending review');
- });
- it('should return "review submitted" (both doReview = true & doAssessment = false)', () => {
- component.assessment.type = 'moderated';
- component.doAssessment = false;
- component.doReview = true;
- component.submitted = true;
- expect(component.footerText()).toEqual('review submitted');
- });
- it('should return "submitted" (Assessment type != moderated, doReview & doAssessment = true)', () => {
- component.assessment.type = 'not moderated';
- component.doAssessment = true;
- component.doReview = true;
- component.submitted = true;
- expect(component.footerText()).toEqual('submitted');
- });
- it('should return "submitted" (doReview & doAssessment = false)', () => {
- component.assessment.type = 'not moderated';
- component.doAssessment = false;
- component.doReview = false;
- component.submitted = true;
- expect(component.footerText()).toEqual('');
- });
- it('should return "review submitted"', () => {
- component.assessment.type = 'moderated';
- component.doReview = true;
- component.submitted = true;
- expect(component.footerText()).toEqual('review submitted');
- });
- it('should return "submitted" (submitted & doReview = true)', () => {
- component.assessment.type = 'moderated';
- component.doReview = true;
- component.doAssessment = false;
- component.submitted = true;
- expect(component.footerText()).toEqual('review submitted');
- });
- it('should return "submitted" (submitted & doAssessment = true)', () => {
- component.assessment.type = '';
- component.doReview = false;
- component.doAssessment = true;
- component.submitted = true;
- expect(component.footerText()).toEqual('submitted');
- });
- it('should return feedback available', () => {
- component.submission.status = 'published';
- expect(component.footerText()).toEqual('feedback available');
- });
- it('should return done', () => {
- component.submission.status = 'published';
- component.feedbackReviewed = true;
- expect(component.footerText()).toEqual('done');
- });
- it('should return pending review', () => {
- component.submission.status = 'pending approval';
- expect(component.footerText()).toEqual('pending review');
- });
- it('should return pending review', () => {
- component.submission.status = 'pending review';
- expect(component.footerText()).toEqual('pending review');
- });
- it('should return false - if not submitted & action = review', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.action = 'review';
- expect(component.footerText()).toBeFalsy();
- });
- it('should return false - if not submitted & action = review', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.action = 'review';
- expect(component.footerText()).toBeFalsy();
- });
- it('should return false - if this.submission = false', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.action = '';
- component.submission = null;
- expect(component.footerText()).toBeFalsy();
- });
- });
-
- describe('when testing markReviewFeedbackAsRead()', () => {
- it('should pop up review rating modal', fakeAsync(() => {
- storageSpy.getUser.and.returnValue({ hasReviewRating: true });
- component.markReviewFeedbackAsRead();
- tick();
- expect(assessmentSpy.popUpReviewRating.calls.count()).toBe(1);
- }));
- });
-
- it('showQuestionInfo() should popup info modal', () => {
- component.showQuestionInfo('abc');
- expect(notificationSpy.popUp.calls.count()).toBe(1);
- });
-});
diff --git a/src/app/assessment/assessment.component.ts b/src/app/assessment/assessment.component.ts
deleted file mode 100644
index 01d85d22d..000000000
--- a/src/app/assessment/assessment.component.ts
+++ /dev/null
@@ -1,807 +0,0 @@
-import { Component, Input, NgZone, Output, EventEmitter } from '@angular/core';
-import { Router, ActivatedRoute, ParamMap } from '@angular/router';
-import { AssessmentService, Assessment, Submission, Review, AssessmentSubmitParams } from './assessment.service';
-import { UtilsService } from '../services/utils.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { FormGroup, FormControl, Validators } from '@angular/forms';
-import { BrowserStorageService } from '@services/storage.service';
-import { RouterEnter } from '@services/router-enter.service';
-import { SharedService } from '@services/shared.service';
-import { ActivityService } from '../activity/activity.service';
-import { FastFeedbackService } from '../fast-feedback/fast-feedback.service';
-import { interval, timer, Subscription } from 'rxjs';
-import { map, takeUntil } from 'rxjs/operators';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-
-const SAVE_PROGRESS_TIMEOUT = 10000;
-
-@Component({
- selector: 'app-assessment',
- templateUrl: 'assessment.component.html',
- styleUrls: ['assessment.component.scss']
-})
-export class AssessmentComponent extends RouterEnter {
- @Input() inputId: number;
- @Input() inputActivityId: number;
- @Input() inputSubmissionId: number;
- @Input() inputContextId: number;
- @Input() inputAction: string;
- @Input() fromPage = '';
- @Output() navigate = new EventEmitter();
- @Output() changeStatus = new EventEmitter();
- getAssessment: Subscription;
- getSubmission: Subscription;
- routeUrl = '/assessment/';
- // assessment id
- id: number;
- // activity id
- activityId: number;
- // context id
- contextId: number;
- submissionId: number;
- assessment: Assessment = {
- name: '',
- type: '',
- description: '',
- isForTeam: false,
- dueDate: '',
- isOverdue: false,
- groups: [],
- pulseCheck: false,
- };
- submission: Submission = {
- id: 0,
- status: '',
- answers: {},
- submitterName: '',
- modified: '',
- isLocked: false,
- completed: false,
- submitterImage: '',
- reviewerName: ''
- };
- review: Review = {
- id: 0,
- answers: {},
- status: '',
- modified: ''
- };
- pageTitle = 'Assessment';
- // action == 'assessment' is for user to do assessment, including seeing the submission or seeing the feedback. This actually means the current user is the user who should "do" this assessment
- // action == 'reivew' is for user to do review for this assessment. This means the current user is the user who should "review" this assessment
- action: string;
-
- // if doAssessment is true, it means this user is actually doing assessment, meaning it is not started or in progress
- // if action == 'assessment' and doAssessment is false, it means this user is reading the submission or feedback
- doAssessment = false;
- // if doReview is true, it means this user is actually doing review, meaning this assessment is pending review
- // if action == 'review' and doReview is false, it means the review is done and this user is reading the submission and review
- doReview = false;
-
- feedbackReviewed = false;
- loadingAssessment = true;
- questionsForm = new FormGroup({});
- submitting: boolean;
- submitted: boolean;
- savingButtonDisabled = true;
- savingMessage: string;
- // used to prevent manual & automate saving happen at the same time
- saving: boolean;
- continueBtnLoading: boolean;
-
- elIdentities = {}; // virtual element id for accessibility "aria-describedby" purpose
-
- isNotInATeam = false; // to hide assessment content if user not is a team.
-
- constructor (
- public router: Router,
- private route: ActivatedRoute,
- private assessmentService: AssessmentService,
- readonly utils: UtilsService,
- private notificationService: NotificationService,
- public storage: BrowserStorageService,
- public sharedService: SharedService,
- private activityService: ActivityService,
- private fastFeedbackService: FastFeedbackService,
- private ngZone: NgZone,
- private newRelic: NewRelicService,
- ) {
- super(router);
- }
-
- get isMobile() {
- return this.utils.isMobile();
- }
-
- /**
- * status of access restriction
- *
- * @return {boolean} cached singlePageAccess in localstorage
- */
- get restrictedAccess() {
- return this.storage.singlePageAccess;
- }
-
- randomCode(type) {
- if (!this.elIdentities[type]) {
- this.elIdentities[type] = this.utils.randomNumber();
- }
-
- return this.elIdentities[type];
- }
-
- // force every navigation happen under radar of angular
- private _navigate(direction, params?): Promise {
- if (this.utils.isMobile()) {
- // redirect to topic/assessment page for mobile
- return this.ngZone.run(() => {
- return this.router.navigate(direction, params);
- });
- } else {
- // emit to parent component(events component)
- if (['events', 'reviews'].includes(direction[1])) {
- this.navigate.emit();
- return ;
- }
- // emit event to parent component(task component)
- switch (direction[0]) {
- case 'topic':
- this.navigate.emit({
- type: 'topic',
- topicId: direction[2]
- });
- break;
- case 'assessment':
- this.navigate.emit({
- type: 'assessment',
- contextId: direction[3],
- assessmentId: direction[4]
- });
- break;
- default:
- return this.ngZone.run(() => {
- return this.router.navigate(direction, params);
- });
- }
- }
- }
-
- private _initialise() {
- this.assessment = {
- name: '',
- type: '',
- description: '',
- isForTeam: false,
- dueDate: '',
- isOverdue: false,
- groups: [],
- pulseCheck: false,
- };
- this.submission = {
- id: 0,
- status: '',
- answers: {},
- submitterName: '',
- modified: '',
- isLocked: false,
- completed: false,
- submitterImage: '',
- reviewerName: ''
- };
- this.review = {
- id: 0,
- answers: {},
- status: '',
- modified: ''
- };
- this.loadingAssessment = true;
- this.saving = false;
- this.doAssessment = false;
- this.doReview = false;
- this.feedbackReviewed = false;
- this.questionsForm = new FormGroup({});
- this.submitting = false;
- this.submitted = false;
- this.savingButtonDisabled = false;
- this.savingMessage = '';
- this.continueBtnLoading = false;
- this.id = null;
- this.activityId = null;
- this.contextId = null;
- this.submissionId = null;
- this.isNotInATeam = false;
- }
-
- onEnter() {
- this._initialise();
-
- if (this.inputAction) {
- this.action = this.inputAction;
- } else {
- this.action = this.route.snapshot.data.action;
- }
- if (!this.fromPage) {
- this.fromPage = this.route.snapshot.paramMap.get('from');
- }
- if (!this.fromPage) {
- this.fromPage = this.route.snapshot.data.from;
- }
- if (this.inputId) {
- this.id = +this.inputId;
- } else {
- this.id = +this.route.snapshot.paramMap.get('id');
- }
- if (this.inputActivityId) {
- this.activityId = +this.inputActivityId;
- } else {
- this.activityId = +this.route.snapshot.paramMap.get('activityId');
- }
- if (this.inputContextId) {
- this.contextId = +this.inputContextId;
- } else {
- this.contextId = +this.route.snapshot.paramMap.get('contextId');
- }
- if (this.inputSubmissionId) {
- this.submissionId = +this.inputSubmissionId;
- } else {
- this.submissionId = +this.route.snapshot.paramMap.get('submissionId');
- }
-
- // get assessment structure and populate the question form
- this.assessmentService.getAssessment(this.id, this.action, this.activityId, this.contextId, this.submissionId)
- .subscribe(
- result => {
- this.assessment = result.assessment;
- this.newRelic.setPageViewName(`Assessment: ${this.assessment.name} ID: ${this.id}`);
- this.populateQuestionsForm();
- this.loadingAssessment = false;
- this._handleSubmissionData(result.submission);
- // display pop up if it is team assessment and user is not in team
- if (this.doAssessment && this.assessment.isForTeam && !this.storage.getUser().teamId) {
- this.isNotInATeam = true;
- return this.notificationService.alert({
- message: 'Currently you are not in a team, please reach out to your Administrator or Coordinator to proceed with next steps.',
- buttons: [
- {
- text: 'OK',
- role: 'cancel',
- handler: () => {
- this.goToNextTask();
- }
- }
- ]
- });
- }
- this.isNotInATeam = false;
- this._handleReviewData(result.review);
- },
- error => {
- this.newRelic.noticeError(error);
- }
- );
- }
-
- private _handleSubmissionData(submission) {
- this.submission = submission;
- // If team assessment is locked, set the page to readonly mode.
- // set doAssessment, doReview to false - when assessment is locked, user can't do both.
- // set submission status to done - we need to show readonly answers in question components.
- if (this.submission && this.submission.isLocked) {
- this.doAssessment = false;
- this.doReview = false;
- this.savingButtonDisabled = true;
- this.submission.status = 'done';
- return;
- }
-
- // this component become a page for doing assessment if
- // - submission is empty or
- // - submission.status is 'in progress'
- if (this.utils.isEmpty(this.submission) || this.submission.status === 'in progress') {
- this.pageTitle = 'Submit your work';
- this.doAssessment = true;
- this.doReview = false;
- if (this.submission && this.submission.status === 'in progress') {
- this.savingMessage = 'Last saved ' + this.utils.timeFormatter(this.submission.modified);
- this.savingButtonDisabled = false;
- }
- return;
- }
-
- this.pageTitle = 'View submission';
-
- if (this.assessment.type === 'moderated') {
- // this component become a page for doing review, if
- // - the submission status is 'pending review' and
- // - this.action is review
- if (this.submission.status === 'pending review' && this.action === 'review') {
- this.pageTitle = 'Provide feedback';
- this.doReview = true;
- }
-
- if (this.submission.status === 'published') {
- this.pageTitle = 'View feedback';
- }
- }
-
- this.feedbackReviewed = this.submission.completed;
- }
-
- private _handleReviewData(review) {
- this.review = review;
- if (!review && this.action === 'review' && !this.doReview) {
- return this.notificationService.alert({
- message: 'There are no assessments to review.',
- buttons: [
- {
- text: 'OK',
- role: 'cancel',
- handler: () => {
- this._navigate(['app', 'home']);
- }
- }
- ]
- });
- }
- if (this.doReview && review.status === 'in progress') {
- this.savingMessage = 'Last saved ' + this.utils.timeFormatter(review.modified);
- this.savingButtonDisabled = false;
- }
- }
-
-
- ionViewWillLeave() {
- this.sharedService.stopPlayingVideos();
- }
-
- /**
- * a consistent comparison logic to ensure mandatory status
- * @param {question} question
- */
- private isRequired(question) {
- let role = 'submitter';
-
- if (this.action === 'review') {
- role = 'reviewer';
- }
-
- return (question.isRequired && question.audience.includes(role));
- }
-
- // Populate the question form with FormControls.
- // The name of form control is like 'q-2' (2 is an example of question id)
- populateQuestionsForm() {
- let validator = [];
- this.assessment.groups.forEach(group => {
- group.questions.forEach(question => {
- // check if the compulsory is mean for current user's role
- if (this.isRequired(question)) {
- // put 'required' validator in FormControl
- validator = [Validators.required];
- } else {
- validator = [];
- }
-
- this.questionsForm.addControl('q-' + question.id, new FormControl('', validator));
- });
- });
- }
-
- /**
- * Navigate back to the previous page
- */
- navigateBack(): Promise {
- const referrer = this.storage.getReferrer();
- if (this.utils.has(referrer, 'url') && referrer.route === 'assessment') {
- this.newRelic.actionText('Navigating to external return URL from Assessment');
- this.utils.redirectToUrl(referrer.url);
- return Promise.resolve(true);
- }
- if (this.fromPage && this.fromPage === 'reviews') {
- return this._navigate(['app', 'reviews']);
- }
- if (this.fromPage && this.fromPage === 'events') {
- return this._navigate(['app', 'events']);
- }
- if (this.activityId) {
- return this._navigate(['app', 'activity', this.activityId ]);
- }
- return this._navigate(['app', 'home']);
- }
-
- /**
- * When user click on the back button
- */
- back(): Promise {
- this.newRelic.actionText('Back to previous page.');
-
- if (this.action === 'assessment'
- && this.submission
- && this.submission.status === 'published'
- && !this.feedbackReviewed) {
- return this.notificationService.alert({
- header: `Mark feedback as read?`,
- message: 'Would you like to mark the feedback as read?',
- buttons: [
- {
- text: 'No',
- handler: () => this.navigateBack(),
- },
- {
- text: 'Yes',
- handler: () => this.markReviewFeedbackAsRead().then(() => {
- return this.navigateBack();
- })
- }
- ]
- });
- } else {
- // force saving progress
- this.submit(true , true, true);
- return this.navigateBack();
- }
- }
-
- /**
- * @name compulsoryQuestionsAnswered
- * @description to check if every compulsory question has been answered
- * @param {Object[]} answers a list of answer object (in submission-based format)
- */
- compulsoryQuestionsAnswered(answers): object[] {
- const missing = [];
- const answered = {};
- this.utils.each(answers, answer => {
- answered[answer.questionId] = answer;
- });
-
- this.assessment.groups.forEach(group => {
- group.questions.forEach(question => {
- if (this.isRequired(question)) {
- if (this.utils.isEmpty(answered[question.id]) || this.utils.isEmpty(answered[question.id].answer)) {
- missing.push(question);
- }
- }
- });
- });
-
- return missing;
- }
-
- /**
- * When user click the continue button
- */
- async clickBtnContinue() {
- if (this.submission && this.submission.status === 'published' && !this.feedbackReviewed) {
- await this.markReviewFeedbackAsRead();
- }
- this.goToNextTask();
- }
-
- /**
- * Go to the next task
- */
- goToNextTask() {
- // skip "continue workflow" && instant redirect user, when:
- // - review action (this.action == 'review')
- // - fromPage = events (check AssessmentRoutingModule)
- if (this.action === 'review' ||
- (this.action === 'assessment' && this.fromPage === 'events')
- ) {
- return this.navigateBack();
- }
-
- this.newRelic.actionText('Navigate to next task.');
- this.continueBtnLoading = true;
- this.activityService.gotoNextTask(this.activityId, 'assessment', this.id, this.submitted).then(redirect => {
- this.continueBtnLoading = false;
- if (redirect) {
- this._navigate(redirect);
- }
- });
- }
-
- /**
- * - check if fastfeedback is available
- * - show next sequence if submission successful
- */
- private async pullFastFeedback() {
- this.continueBtnLoading = true;
- // check if this assessment have plus check turn on, if it's on show plus check and toast message
- if (!this.assessment.pulseCheck) {
- this.continueBtnLoading = false;
- return;
- }
- try {
- const modal = await this.fastFeedbackService.pullFastFeedback({ modalOnly: true }).toPromise();
- if (modal && modal.present) {
- await modal.present();
- await modal.onDidDismiss();
- }
- this.continueBtnLoading = false;
- } catch (err) {
- const toasted = await this.notificationService.alert({
- header: 'Error retrieving pulse check data',
- message: err.msg || JSON.stringify(err)
- });
- this.continueBtnLoading = false;
- throw new Error(err);
- }
- }
-
- /**
- * handle submission and autosave
- * @param saveInProgress set true for autosaving or it treat the action as final submision
- * @param goBack use to unlock team assessment when leave assessment by clicking back button
- * @param isManualSave use to detect manual progress save
- */
- async submit(saveInProgress: boolean, goBack?: boolean, isManualSave?: boolean): Promise {
-
- /**
- * checking if this is a submission or progress save
- * - if it's a submission
- * - assign true to saving variable to disable duplicate saving
- * - change submitting variable value to true
- * - if it's a progress save
- * - if this is a manual save or there is no other auto save in progress
- * - change saving variable value to true to disable duplicate saving
- * - make manual save button disable
- * - change savingMessage variable value to 'Saving...' to show save in progress
- * - if this is not manual save or there is one save in progress
- * - do nothing
- */
- if (saveInProgress) {
- if (isManualSave || !this.saving) {
- this.savingMessage = 'Saving...';
- this.savingButtonDisabled = true;
- } else {
- return;
- }
- } else {
- this.submitting = true;
- }
- this.saving = true;
-
- const answers = [];
- let questionId = 0;
- let assessment: AssessmentSubmitParams;
-
- assessment = {
- id: this.id
- };
- if (saveInProgress) {
- assessment.inProgress = true;
- }
- if (this.submission && this.submission.id) {
- assessment.submissionId = this.submission.id;
- }
-
- // form submission answers
- if (this.doAssessment) {
- assessment.contextId = this.contextId;
-
- if (this.assessment.isForTeam && goBack) {
- assessment.unlock = true;
- }
- this.utils.each(this.questionsForm.value, (value, key) => {
- questionId = +key.replace('q-', '');
- let answer;
- if (value) {
- answer = value;
- } else {
- this.assessment.groups.forEach(group => {
- const currentQuestion = group.questions.find(question => {
- return question.id === questionId;
- });
- if (currentQuestion && currentQuestion.type === 'multiple') {
- answer = [];
- } else {
- answer = null;
- }
- });
- }
- answers.push({
- questionId: questionId,
- answer: answer
- });
- });
- }
-
- // form feedback answers
- if (this.doReview) {
- assessment = Object.assign(assessment, {
- reviewId: this.review.id
- });
-
- this.utils.each(this.questionsForm.value, (answer, key) => {
- if (!this.utils.isEmpty(answer)) {
- answer.questionId = +key.replace('q-', '');
- answers.push(answer);
- }
- });
- }
-
- // check if all required questions have answer when assessment done
- const requiredQuestions = this.compulsoryQuestionsAnswered(answers);
- if (!saveInProgress && requiredQuestions.length > 0) {
- this.submitting = false;
- // display a pop up if required question not answered
- return this.notificationService.popUp('shortMessage', {
- message: 'Required question answer missing!'
- });
- }
-
- // save the submission/feedback
- this.assessmentService.saveAnswers(
- assessment,
- answers,
- this.action
- ).subscribe(
- result => {
- if (saveInProgress) {
- this.newRelic.actionText('Saved progress.');
- // display message for successfull saved answers
- this.savingMessage = 'Last saved ' + this._getCurrentTime();
- this.savingButtonDisabled = false;
- } else {
- this.newRelic.actionText('Assessment Submitted.');
- this.submitting = false;
- this.submitted = true;
- this.changeStatus.emit({
- id: +this.id,
- status: this.assessment.type === 'moderated' ? 'pending review' : 'done'
- });
- // disabled all forms controls
- Object.keys(this.questionsForm.controls).forEach(key => this.questionsForm.controls[key].disable());
- return this.pullFastFeedback();
- }
- },
- (err: { msg?: string, message?: string }) => {
- this.newRelic.noticeError(JSON.stringify(err));
-
- this.submitting = false;
- this.savingButtonDisabled = false;
- if (saveInProgress) {
- // display message when saving answers failed
- this.savingMessage = 'Auto save unavailable';
- } else {
- // display a pop up if submission failed
- this.notificationService.alert({
- header: 'Submission failed',
- message: 'Please refresh the page and try it again later',
- buttons: [
- {
- text: 'OK',
- role: 'cancel'
- }
- ]
- });
- throw new Error(err.msg || err.message || JSON.stringify(err));
- }
- }
- );
- // if saveInProgress and isManualSave true renabling save without wait 10 second
- if (saveInProgress && isManualSave) {
- this.saving = false;
- }
- // if timeout, reset this.saving flag to false, to enable saving again
- setTimeout(() => this.saving = false, SAVE_PROGRESS_TIMEOUT);
- }
-
- /**
- * Mark review feedback as read
- */
- async markReviewFeedbackAsRead(): Promise {
- // do nothing if feedback is already mark as read
- if (this.feedbackReviewed) {
- return;
- }
- this.continueBtnLoading = true;
- let result;
- this.newRelic.actionText('Waiting for review feedback read.');
- // Mark feedback as read
- try {
- result = await this.assessmentService.saveFeedbackReviewed(this.submission.id).toPromise();
- this.feedbackReviewed = true;
- this.newRelic.actionText('Review feedback read.');
- this.continueBtnLoading = false;
- } catch (err) {
- this.continueBtnLoading = false;
- // @TODO - Removed the popup for now until we implement proper way to handle API error
- /**const toasted = await this.notificationService.alert({
- header: 'Marking feedback as read failed',
- message: err.msg || JSON.stringify(err)
- });
- throw new Error(err);
- **/
- }
-
- // After marking feedback as read, popup review rating modal if
- // 1. review is successfully marked as read (from above) - removing because above @TODO reason
- // 2. hasReviewRating (activation): program configuration is set to enable review rating
- if (!this.storage.getUser().hasReviewRating) {
- return;
- }
-
- this.continueBtnLoading = true;
- this.newRelic.actionText('Waiting for review rating API response.');
- try {
- // display review rating modal
- await this.assessmentService.popUpReviewRating(this.review.id, false);
- this.continueBtnLoading = false;
- } catch (err) {
- const msg = 'Can not get review rating information';
- this.newRelic.noticeError(msg);
- const toasted = await this.notificationService.alert({
- header: msg,
- message: err.msg || JSON.stringify(err)
- });
- this.continueBtnLoading = false;
- throw new Error(err);
- }
- }
-
- showQuestionInfo(info) {
- this.newRelic.actionText('Read question info.');
- this.notificationService.popUp('shortMessage', {message: info});
- }
-
- private _getCurrentTime() {
- return new Intl.DateTimeFormat('en-US', {
- hour12: true,
- hour: 'numeric',
- minute: 'numeric'
- }).format(new Date());
- }
-
- hasFooter() {
- return this.doAssessment || this.doReview || this.footerText();
- }
-
- submissionStatus() {
- switch (this.submission.status) {
- case 'published':
- if (this.feedbackReviewed) {
- return 'done';
- }
- return 'feedback available';
- case 'pending approval':
- return 'pending review';
- default:
- return this.submission.status;
- }
- }
-
- doAssessmentDoReviewStatus() {
- if (this.submitting) {
- return 'submitting';
- }
-
- if (this.submitted) {
- if (this.assessment.type === 'moderated') {
- if (this.doAssessment) {
- return 'pending review';
- }
- return 'review submitted';
- }
- return 'submitted';
- }
- // display the submit button, don't need the text in the footer
- return false;
- }
-
- /**
- * Get the text on the left of the footer.
- * Return false if it shouldn't be displayed
- */
- footerText(): string | boolean {
- // if it is to do assessment or do review
- if (this.doAssessment || this.doReview) {
- return this.doAssessmentDoReviewStatus();
- } else if (this.action === 'review' || !this.submission) {
- return false;
- } else {
- return this.submissionStatus();
- }
- }
-
-}
diff --git a/src/app/assessment/assessment.module.ts b/src/app/assessment/assessment.module.ts
deleted file mode 100644
index d8281e20a..000000000
--- a/src/app/assessment/assessment.module.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { SharedModule } from '@shared/shared.module';
-import { NgModule } from '@angular/core';
-import { AssessmentRoutingModule } from './assessment-routing.module';
-import { ReactiveFormsModule } from '@angular/forms';
-
-import { AssessmentComponent } from './assessment.component';
-import { QuestionsModule } from '../questions/questions.module';
-import { ActivityModule } from '../activity/activity.module';
-
-@NgModule({
- imports: [
- SharedModule,
- AssessmentRoutingModule,
- ReactiveFormsModule,
- QuestionsModule,
- ActivityModule,
- ],
- declarations: [
- AssessmentComponent
- ],
- exports: [
- SharedModule,
- ActivityModule,
- AssessmentComponent
- ]
-})
-
-export class AssessmentModule {
-}
diff --git a/src/app/assessment/assessment.service.spec.ts b/src/app/assessment/assessment.service.spec.ts
deleted file mode 100644
index 3beec8ab8..000000000
--- a/src/app/assessment/assessment.service.spec.ts
+++ /dev/null
@@ -1,568 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { of } from 'rxjs';
-import { RequestService } from '@shared/request/request.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { UtilsService } from '@services/utils.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { AssessmentService, AssessmentSubmitParams } from './assessment.service';
-import { TestUtils } from '@testing/utils';
-
-describe('AssessmentService', () => {
- let service: AssessmentService;
- let requestSpy: jasmine.SpyObj;
- let notificationSpy: jasmine.SpyObj;
- let utils: UtilsService;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- providers: [
- AssessmentService,
- {
- provide: UtilsService,
- useClass: TestUtils,
- },
- {
- provide: NotificationService,
- useValue: jasmine.createSpyObj('NotificationService', ['modal'])
- },
- {
- provide: RequestService,
- useValue: jasmine.createSpyObj('RequestService', ['get', 'post', 'graphQLWatch', 'graphQLMutate', 'apiResponseFormatError'])
- },
- {
- provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', {
- getUser: {
- name: 'Test',
- projectId: 1
- }
- })
- },
- ]
- });
- service = TestBed.inject(AssessmentService);
- requestSpy = TestBed.inject(RequestService) as jasmine.SpyObj;
- notificationSpy = TestBed.inject(NotificationService) as jasmine.SpyObj;
- utils = TestBed.inject(UtilsService);
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-
- describe('when testing getAssessment()', () => {
- let requestResponse;
- let expectedAssessment, expectedSubmission, expectedReview;
- let assessment, group0, question0, question1, question2, group1, question3, question4, submission, review;
- beforeEach(() => {
- requestResponse = {
- data: {
- assessment: {
- id: 1,
- name: 'test',
- type: 'quiz',
- description: 'des',
- isTeam: false,
- dueDate: '2019-02-02',
- pulseCheck: false,
- groups: [
- {
- name: 'g name',
- description: 'g des',
- questions: [
- {
- id: 1,
- name: 'test name 1',
- description: 'des 1',
- type: 'text',
- isRequired: true,
- hasComment: true,
- audience: ['submitter']
- },
- {
- id: 2,
- name: 'test name 2',
- description: 'des 2',
- type: 'oneof',
- isRequired: true,
- hasComment: true,
- audience: ['reviewer'],
- choices: [
- {
- id: 21,
- name: 'choice name 1'
- },
- {
- id: 22,
- name: 'choice name 2'
- }
- ]
- },
- {
- id: 3,
- name: 'test name 3',
- description: 'des 3',
- type: 'multiple',
- isRequired: true,
- hasComment: true,
- audience: ['submitter', 'reviewer'],
- choices: [
- {
- id: 31,
- name: 'choice name 1',
- description: 'choice des 1'
- },
- {
- id: 32,
- name: 'choice name 2',
- description: 'choice des 2'
- }
- ]
- }
- ]
- },
- {
- name: 'g name',
- description: 'g des',
- questions: [
- {
- id: 11,
- name: 'test name 11',
- description: 'des 11',
- type: 'file',
- isRequired: true,
- hasComment: true,
- audience: ['submitter', 'reviewer'],
- fileType: 'any'
- },
- {
- id: 12,
- name: 'test name 12',
- description: 'des 12',
- type: 'team member selector',
- isRequired: true,
- hasComment: true,
- audience: ['submitter', 'reviewer'],
- teamMembers: [
- {
- id: 121,
- userName: 'member name 1'
- },
- {
- id: 122,
- userName: 'member name 2'
- }
- ]
- },
- ]
- }
- ],
- submissions: [
- {
- id: 1,
- status: 'published',
- modified: '2019-02-02',
- locked: false,
- completed: false,
- submitter: {
- name: 'test name',
- image: ''
- },
- answers: [
- {
- questionId: 1,
- answer: 'abc'
- },
- {
- questionId: 2,
- answer: 21
- },
- {
- questionId: 3,
- answer: [31]
- },
- {
- questionId: 11,
- answer: ''
- },
- {
- questionId: 12,
- answer: '{"id": 121,"userName": "member name 1"}'
- }
- ],
- review: {
- id: 2,
- status: 'done',
- modified: '2019-02-02',
- reviewer: {
- name: 'test reviewer name'
- },
- answers: [
- {
- questionId: 1,
- answer: 'abc',
- comment: null
- },
- {
- questionId: 2,
- answer: 21,
- comment: 'def'
- },
- {
- questionId: 3,
- answer: [31],
- comment: 'def'
- },
- {
- questionId: 11,
- answer: '',
- comment: 'def'
- },
- {
- questionId: 12,
- answer: null,
- comment: null
- }
- ]
- }
- }
- ]
- }
- }
- };
- assessment = requestResponse.data.assessment;
- group0 = assessment.groups[0];
- // text
- question0 = group0.questions[0];
- // oneof
- question1 = group0.questions[1];
- // multiple
- question2 = group0.questions[2];
- group1 = assessment.groups[1];
- // file
- question3 = group1.questions[0];
- // team member selector
- question4 = group1.questions[1];
- expectedAssessment = {
- name: assessment.name,
- type: assessment.type,
- description: assessment.description,
- isForTeam: assessment.isTeam,
- dueDate: assessment.dueDate,
- isOverdue: assessment.dueDate ? utils.timeComparer(assessment.dueDate) < 0 : false,
- pulseCheck: assessment.pulseCheck,
- groups: [
- {
- name: group0.name,
- description: group0.description,
- questions: [
- {
- id: question0.id,
- name: question0.name,
- type: question0.type,
- description: question0.description,
- isRequired: question0.isRequired,
- canComment: question0.hasComment,
- canAnswer: question0.audience.includes('submitter'),
- audience: question0.audience,
- submitterOnly: true,
- reviewerOnly: false
- },
- {
- id: question1.id,
- name: question1.name,
- type: question1.type,
- description: question1.description,
- isRequired: question1.isRequired,
- canComment: question1.hasComment,
- canAnswer: question1.audience.includes('submitter'),
- audience: question1.audience,
- submitterOnly: false,
- reviewerOnly: true,
- info: '',
- choices: [
- {
- id: question1.choices[0].id,
- name: question1.choices[0].name,
- explanation: null
- },
- {
- id: question1.choices[1].id,
- name: question1.choices[1].name,
- explanation: null
- }
- ]
- },
- {
- id: question2.id,
- name: question2.name,
- type: question2.type,
- description: question2.description,
- isRequired: question2.isRequired,
- canComment: question2.hasComment,
- canAnswer: question2.audience.includes('submitter'),
- audience: question2.audience,
- submitterOnly: false,
- reviewerOnly: false,
- info: `Choice Description: ${question2.choices[0].name} ` +
- `- ${question2.choices[0].description}
${question2.choices[1].name} ` +
- `- ${question2.choices[1].description}
`,
- choices: [
- {
- id: question2.choices[0].id,
- name: question2.choices[0].name,
- explanation: null
- },
- {
- id: question2.choices[1].id,
- name: question2.choices[1].name,
- explanation: null
- }
- ]
- }
- ]
- },
- {
- name: group1.name,
- description: group1.description,
- questions: [
- {
- id: question3.id,
- name: question3.name,
- type: question3.type,
- description: question3.description,
- isRequired: question3.isRequired,
- canComment: question3.hasComment,
- canAnswer: question3.audience.includes('submitter'),
- audience: question3.audience,
- submitterOnly: false,
- reviewerOnly: false,
- fileType: question3.fileType
- },
- {
- id: question4.id,
- name: question4.name,
- type: question4.type,
- description: question4.description,
- isRequired: question4.isRequired,
- canComment: question4.hasComment,
- canAnswer: question4.audience.includes('submitter'),
- audience: question4.audience,
- submitterOnly: false,
- reviewerOnly: false,
- teamMembers: [
- {
- key: JSON.stringify(question4.teamMembers[0]),
- userName: question4.teamMembers[0].userName
- },
- {
- key: JSON.stringify(question4.teamMembers[1]),
- userName: question4.teamMembers[1].userName
- }
- ]
- }
- ]
- }
- ]
- };
- submission = assessment.submissions[0];
- expectedSubmission = {
- id: submission.id,
- status: submission.status,
- submitterName: submission.submitter.name,
- submitterImage: submission.submitter.image,
- modified: submission.modified,
- isLocked: submission.locked,
- completed: submission.completed,
- reviewerName: submission.review.reviewer.name,
- answers: {
- 1: {
- answer: submission.answers[0].answer
- },
- 2: {
- answer: submission.answers[1].answer
- },
- 3: {
- answer: submission.answers[2].answer
- },
- 11: {
- answer: submission.answers[3].answer
- },
- 12: {
- answer: submission.answers[4].answer
- }
- }
- };
- review = submission.review;
- expectedReview = {
- id: review.id,
- status: review.status,
- modified: review.modified,
- answers: {
- 1: {
- answer: review.answers[0].answer,
- comment: review.answers[0].comment
- },
- 2: {
- answer: review.answers[1].answer,
- comment: review.answers[1].comment
- },
- 3: {
- answer: review.answers[2].answer,
- comment: review.answers[2].comment
- },
- 11: {
- answer: review.answers[3].answer,
- comment: review.answers[3].comment
- },
- 12: {
- answer: review.answers[4].answer,
- comment: review.answers[4].comment
- }
- }
- };
- });
-
- afterEach(() => {
- requestSpy.graphQLWatch.and.returnValue(of(requestResponse));
- service.getAssessment(1, 'assessment', 2, 3).subscribe(
- result => {
- expect(result.assessment).toEqual(expectedAssessment);
- expect(result.submission).toEqual(expectedSubmission);
- expect(result.review).toEqual(expectedReview);
- }
- );
- expect(requestSpy.graphQLWatch.calls.count()).toBe(1);
- });
-
- it('should get correct assessment data', () => { });
-
- it(`should not include a question group if there's no question inside`, () => {
- // if a question group doesn't have question
- requestResponse.data.assessment.groups[1].questions = [];
- requestResponse.data.assessment.submissions[0].answers.splice(3, 2);
- requestResponse.data.assessment.submissions[0].review.answers.splice(3, 2);
- // the expected result won't contain that group
- expectedAssessment.groups.splice(1, 1);
- delete expectedSubmission.answers[11];
- delete expectedSubmission.answers[12];
- delete expectedReview.answers[11];
- delete expectedReview.answers[12];
- });
-
- it('should get correct submission data without review', () => {
- requestResponse.data.assessment.submissions[0].review = null;
- expectedSubmission.reviewerName = null;
- expectedReview = null;
- });
- });
-
- describe('when testing saveAnswers()', () => {
- const answers = [
- { questionId: 123, answer: 'abc' },
- { questionId: 124, answer: 456 },
- { questionId: 125, answer: [3, 4] },
- { questionId: 126, answer: [3] },
- { questionId: 127, answer: { filename: 'abc.png' } }
- ];
- beforeEach(() => {
- requestSpy.graphQLMutate.and.returnValue(of(true));
- });
-
- it('should save assessment answers correctly', () => {
- const assessment = {
- id: 1,
- inProgress: true,
- contextId: 2
- };
- service.saveAnswers(assessment, answers, 'assessment').subscribe();
- expect(requestSpy.graphQLMutate.calls.first().args[0]).toContain('submitAssessment');
- expect(requestSpy.graphQLMutate.calls.first().args[1]).toEqual({
- assessmentId: assessment.id,
- inProgress: assessment.inProgress,
- contextId: assessment.contextId,
- answers: answers
- });
- });
-
- it('should save assessment answers correctly with submission id', () => {
- const assessment = {
- id: 1,
- inProgress: true,
- contextId: 2,
- submissionId: 3,
- unlock: true
- };
- service.saveAnswers(assessment, answers, 'assessment').subscribe();
- expect(requestSpy.graphQLMutate.calls.first().args[0]).toContain('submitAssessment');
- expect(requestSpy.graphQLMutate.calls.first().args[1]).toEqual({
- assessmentId: assessment.id,
- inProgress: assessment.inProgress,
- contextId: assessment.contextId,
- submissionId: assessment.submissionId,
- unlock: assessment.unlock,
- answers: answers
- });
- });
-
- it('should save review answers correctly', () => {
- const assessment = {
- id: 1,
- inProgress: true,
- submissionId: 3,
- reviewId: 4
- };
- service.saveAnswers(assessment, answers, 'review').subscribe();
- expect(requestSpy.graphQLMutate.calls.first().args[0]).toContain('submitReview');
- expect(requestSpy.graphQLMutate.calls.first().args[1]).toEqual({
- assessmentId: assessment.id,
- inProgress: assessment.inProgress,
- submissionId: assessment.submissionId,
- reviewId: assessment.reviewId,
- answers: answers
- });
- });
-
- it('should return success false if action not correct', () => {
- const assessment = {
- id: 1,
- inProgress: true
- };
- service.saveAnswers(assessment, answers, 'incorrect').subscribe(res => expect(res).toBe(false));
- expect(requestSpy.graphQLMutate.calls.count()).toBe(0);
- });
- });
-
- describe('when testing saveFeedbackReviewed()', () => {
- it('should post correct data', () => {
- service.saveFeedbackReviewed(11);
- expect(requestSpy.post.calls.count()).toBe(1);
- expect(requestSpy.post.calls.first().args[0].data).toEqual({
- project_id: 1,
- identifier: 'AssessmentSubmission-11',
- is_done: true
- });
- });
- });
-
- describe('when testing popUpReviewRating()', () => {
- it('should pass the correct data to notification modal', () => {
- service.popUpReviewRating(1, ['home']);
- expect(notificationSpy.modal.calls.count()).toBe(1);
- expect(notificationSpy.modal.calls.first().args[1]).toEqual({
- reviewId: 1,
- redirect: ['home']
- });
- });
- });
-
- describe('when testing checkReviewer()', () => {
- it('should return null if no reviewer passed in', () => {
- expect(service.checkReviewer(null)).toEqual(null);
- });
- it('should return null if reviewer is the current person', () => {
- expect(service.checkReviewer({ name: 'Test' })).toEqual(null);
- });
- });
-
-});
diff --git a/src/app/assessment/assessment.service.ts b/src/app/assessment/assessment.service.ts
deleted file mode 100644
index 6bbb0c777..000000000
--- a/src/app/assessment/assessment.service.ts
+++ /dev/null
@@ -1,451 +0,0 @@
-import { Injectable } from '@angular/core';
-import { of } from 'rxjs';
-import { map } from 'rxjs/operators';
-import { RequestService } from '@shared/request/request.service';
-import { UtilsService } from '@services/utils.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { ReviewRatingComponent } from '../review-rating/review-rating.component';
-import { DomSanitizer } from '@angular/platform-browser';
-
-/**
- * @name api
- * @description list of api endpoint involved in this service
- * @type {Object}
- */
-const api = {
- post: {
- submissions: 'api/assessment_submissions.json',
- reviews: 'api/feedback_submissions.json',
- todoitem: 'api/v2/motivations/todo_item/edit.json'
- }
-};
-
-export interface AssessmentSubmitParams {
- id: number;
- inProgress?: boolean;
- contextId?: number;
- reviewId?: number;
- submissionId?: number;
- unlock?: boolean;
-}
-
-export interface Assessment {
- name: string;
- type: string;
- description: string;
- isForTeam: boolean;
- dueDate?: string;
- isOverdue?: boolean;
- groups: Array;
- pulseCheck: boolean;
-}
-
-export interface Group {
- name: string;
- description: string;
- questions: Array;
-}
-
-export interface Question {
- id: number;
- name: string;
- type: string;
- fileType?: string;
- description: string;
- info?: string;
- isRequired: boolean;
- canComment: boolean;
- canAnswer: boolean;
- choices?: Array;
- teamMembers?: Array;
- audience: string[];
- submitterOnly?: boolean;
- reviewerOnly?: boolean;
-}
-
-export interface Choice {
- id: number;
- name: string;
- explanation?: string | any;
-}
-
-export interface TeamMember {
- key: string;
- userName: string;
-}
-
-export interface Submission {
- id: number;
- status: string;
- answers: any;
- submitterName: string;
- modified: string;
- isLocked: boolean;
- completed?: boolean;
- submitterImage: string;
- reviewerName: string | void;
-}
-
-export interface Answer {
- questionId: number;
- answer?: any;
- comment?: string;
-}
-
-export interface Review {
- id: number;
- answers: any;
- status: string;
- modified: string;
-}
-
-@Injectable({
- providedIn: 'root'
-})
-
-export class AssessmentService {
- questions = {};
-
- constructor(
- private request: RequestService,
- private utils: UtilsService,
- private storage: BrowserStorageService,
- private notification: NotificationService,
- public sanitizer: DomSanitizer,
- ) { }
-
- getAssessment(id, action, activityId, contextId, submissionId?) {
- return this.request.graphQLWatch(
- `query getAssessment($assessmentId: Int!, $reviewer: Boolean!, $activityId: Int!, $contextId: Int!, $submissionId: Int) {
- assessment(id:$assessmentId, reviewer:$reviewer, activityId:$activityId) {
- name type description dueDate isTeam pulseCheck
- groups{
- name description
- questions{
- id name description type isRequired hasComment audience fileType
- choices{
- id name explanation description
- }
- teamMembers{
- userId userName teamId
- }
- }
- }
- submissions(id:$submissionId, contextId:$contextId) {
- id status completed modified locked
- submitter {
- name image
- }
- answers{
- questionId answer
- }
- review {
- id status modified
- reviewer { name }
- answers {
- questionId answer comment
- }
- }
- }
- }
- }`,
- {
- assessmentId: id,
- reviewer: action === 'review',
- activityId: activityId,
- submissionId: submissionId || null,
- contextId: contextId
- },
- {
- noCache: true
- }
- )
- .pipe(map(res => {
- return {
- assessment: this._normaliseAssessment(res.data, action),
- submission: this._normaliseSubmission(res.data),
- review: this._normaliseReview(res.data, action)
- };
- }));
- }
-
- private _normaliseAssessment(data, action): Assessment {
- if (!data.assessment) {
- return null;
- }
- const assessment = {
- name: data.assessment.name,
- type: data.assessment.type,
- description: data.assessment.description,
- isForTeam: data.assessment.isTeam,
- dueDate: data.assessment.dueDate,
- isOverdue: data.assessment.dueDate ? this.utils.timeComparer(data.assessment.dueDate) < 0 : false,
- pulseCheck: data.assessment.pulseCheck,
- groups: []
- };
- data.assessment.groups.forEach(eachGroup => {
- const questions: Question[] = [];
- if (!eachGroup.questions) {
- return;
- }
- eachGroup.questions.forEach(eachQuestion => {
- this.questions[eachQuestion.id] = eachQuestion;
- const question: Question = {
- id: eachQuestion.id,
- name: eachQuestion.name,
- type: eachQuestion.type,
- description: eachQuestion.description,
- isRequired: eachQuestion.isRequired,
- canComment: eachQuestion.hasComment,
- canAnswer: action === 'review' ? eachQuestion.audience.includes('reviewer') : eachQuestion.audience.includes('submitter'),
- audience: eachQuestion.audience,
- submitterOnly: eachQuestion.audience.length === 1 && eachQuestion.audience.includes('submitter'),
- reviewerOnly: eachQuestion.audience.length === 1 && eachQuestion.audience.includes('reviewer')
- };
- switch (eachQuestion.type) {
- case 'oneof':
- case 'multiple':
- const choices: Choice[] = [];
- let info = '';
- eachQuestion.choices.forEach(eachChoice => {
- choices.push({
- id: eachChoice.id,
- name: eachChoice.name,
- explanation: eachChoice.explanation ? this.sanitizer.bypassSecurityTrustHtml(eachChoice.explanation) : null,
- });
- if (eachChoice.description) {
- info += '' + eachChoice.name + ' - ' + eachChoice.description + '
';
- }
- });
- if (info) {
- // add the title
- info = 'Choice Description: ' + info;
- }
- question.info = info;
- question.choices = choices;
- break;
- // check question file type. if it's not set (it's null) set "any" as default filetype
- case 'file':
- question.fileType = eachQuestion.fileType ? eachQuestion.fileType : 'any';
- break;
-
- case 'team member selector':
- question.teamMembers = [];
- eachQuestion.teamMembers.forEach(eachTeamMember => {
- question.teamMembers.push({
- key: JSON.stringify(eachTeamMember),
- userName: eachTeamMember.userName
- });
- });
- break;
- }
- questions.push(question);
- });
- if (!this.utils.isEmpty(questions)) {
- assessment.groups.push({
- name: eachGroup.name,
- description: eachGroup.description,
- questions: questions
- });
- }
- });
- return assessment;
- }
-
- private _normaliseSubmission(data): Submission {
- if (!this.utils.has(data, 'assessment.submissions') || data.assessment.submissions.length < 1) {
- return null;
- }
- const firstSubmission = data.assessment.submissions[0];
- let submission: Submission = {
- id: firstSubmission.id,
- status: firstSubmission.status,
- submitterName: firstSubmission.submitter.name,
- submitterImage: firstSubmission.submitter.image,
- modified: firstSubmission.modified,
- isLocked: firstSubmission.locked,
- completed: firstSubmission.completed,
- reviewerName: firstSubmission.review ? this.checkReviewer(firstSubmission.review.reviewer) : null,
- answers: {}
- };
- firstSubmission.answers.forEach(eachAnswer => {
- eachAnswer.answer = this._normaliseAnswer(eachAnswer.questionId, eachAnswer.answer);
- submission.answers[eachAnswer.questionId] = {
- answer: eachAnswer.answer
- };
- if (['published', 'done'].includes(submission.status)) {
- submission = this._addChoiceExplanation(eachAnswer, submission);
- }
- });
- return submission;
- }
-
- private _normaliseReview(data, action): Review {
- if (!this.utils.has(data, 'assessment.submissions') || data.assessment.submissions.length < 1) {
- return null;
- }
- const firstSubmission = data.assessment.submissions[0];
- const firstSubmissionReview = firstSubmission.review;
- if (!firstSubmissionReview) {
- return null;
- }
- const review: Review = {
- id: firstSubmissionReview.id,
- status: firstSubmissionReview.status,
- modified: firstSubmissionReview.modified,
- answers: {}
- };
-
- // only get the review answer if the review is published, or it is for the reviewer to see the review
- // i.e. don't display the review answer if it is for submitter and review not published yet
- if (firstSubmission.status !== 'published' && action === 'assessment') {
- return review;
- }
-
- firstSubmissionReview.answers.forEach(eachAnswer => {
- eachAnswer.answer = this._normaliseAnswer(eachAnswer.questionId, eachAnswer.answer);
- review.answers[eachAnswer.questionId] = {
- answer: eachAnswer.answer,
- comment: eachAnswer.comment
- };
- });
- return review;
- }
-
- /**
- * For each question that has choice (oneof & multiple), show the choice explanation in the submission if it is not empty
- */
- private _addChoiceExplanation(submissionAnswer, submission: Submission): Submission {
- const questionId = submissionAnswer.questionId;
- const answer = submissionAnswer.answer;
- // don't do anything if there's no choices
- if (this.utils.isEmpty(this.questions[questionId].choices)) {
- return submission;
- }
- let explanation = '';
- if (Array.isArray(answer)) {
- // multiple question
- this.questions[questionId].choices.forEach(choice => {
- // only display the explanation if it is not empty
- if (answer.includes(choice.id) && !this.utils.isEmpty(choice.explanation)) {
- explanation += choice.name + ' - ' + choice.explanation + '\n';
- }
- });
- } else {
- // oneof question
- this.questions[questionId].choices.forEach(choice => {
- // only display the explanation if it is not empty
- if (answer === choice.id && !this.utils.isEmpty(choice.explanation)) {
- explanation = choice.explanation;
- }
- });
- }
- if (!explanation) {
- return submission;
- }
- // put the explanation in the submission
- const thisExplanation = explanation.replace(/text-align: center;/gi, 'text-align: center; text-align: -webkit-center;');
- submission.answers[questionId].explanation = this.sanitizer.bypassSecurityTrustHtml(thisExplanation);
-
- return submission;
- }
-
- private _normaliseAnswer(questionId, answer) {
- if (this.questions[questionId]) {
- switch (this.questions[questionId].type) {
- case 'oneof':
- // re-format answer from string to number
- if (typeof answer === 'string' && answer.length === 0) {
- // Caution: let answer be null if question wasn't answered previously, 0 could be a possible answer ID
- answer = null;
- } else {
- answer = +answer;
- }
- break;
- case 'multiple':
- if (this.utils.isEmpty(answer)) {
- answer = [];
- }
- if (!Array.isArray(answer)) {
- // re-format json string to array
- answer = JSON.parse(answer);
- }
- // re-format answer from string to number
- answer = answer.map(value => {
- return +value;
- });
- break;
- }
- }
- return answer;
- }
-
- saveAnswers(assessment: AssessmentSubmitParams, answers: Answer[], action: string) {
- if (!['assessment', 'review'].includes(action)) {
- return of(false);
- }
- let paramsFormat = `$assessmentId: Int!, $inProgress: Boolean, $answers: [${(action === 'assessment' ? 'AssessmentSubmissionAnswerInput' : 'AssessmentReviewAnswerInput')}]`;
- let params = 'assessmentId:$assessmentId, inProgress:$inProgress, answers:$answers';
- const variables = {
- assessmentId: assessment.id,
- inProgress: assessment.inProgress,
- answers: answers
- };
- [
- { key: 'submissionId', type: 'Int' },
- { key: 'contextId', type: 'Int!' },
- { key: 'reviewId', type: 'Int' },
- { key: 'unlock', type: 'Boolean' }
- ].forEach(item => {
- if (assessment[item.key]) {
- paramsFormat += `, $${item.key}: ${item.type}`;
- params += `,${item.key}: $${item.key}`;
- variables[item.key] = assessment[item.key];
- }
- });
- return this.request.graphQLMutate(
- `mutation saveAnswers(${paramsFormat}){
- ` + (action === 'assessment' ? `submitAssessment` : `submitReview`) + `(${params})
- }`,
- variables
- );
- }
-
- saveFeedbackReviewed(submissionId) {
- const postData = {
- project_id: this.storage.getUser().projectId,
- identifier: 'AssessmentSubmission-' + submissionId,
- is_done: true
- };
- return this.request.post(
- {
- endPoint: api.post.todoitem,
- data: postData
- });
- }
-
- /**
- * trigger reviewer rating modal
- *
- * @param {number} reviewId submission review record id
- * @param {string[]} redirect array: routeUrl, boolean: disable
- * routing (stay at same component)
- *
- * @return {Promise} deferred ionic modal
- */
- popUpReviewRating(reviewId, redirect: string[] | boolean): Promise {
- return this.notification.modal(ReviewRatingComponent, {
- reviewId,
- redirect
- });
- }
-
- checkReviewer(reviewer): string {
- if (!reviewer) {
- return null;
- }
- return reviewer.name !== this.storage.getUser().name ? reviewer.name : null;
- }
-
-}
diff --git a/src/app/auth/auth-direct-login/auth-direct-login.component.html b/src/app/auth/auth-direct-login/auth-direct-login.component.html
deleted file mode 100644
index a0726f2bf..000000000
--- a/src/app/auth/auth-direct-login/auth-direct-login.component.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
We are redirecting you to the page you want to go to, please be patient
-
-
-
-
diff --git a/src/app/auth/auth-direct-login/auth-direct-login.component.spec.ts b/src/app/auth/auth-direct-login/auth-direct-login.component.spec.ts
deleted file mode 100644
index 2e218c9c6..000000000
--- a/src/app/auth/auth-direct-login/auth-direct-login.component.spec.ts
+++ /dev/null
@@ -1,441 +0,0 @@
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { async, ComponentFixture, TestBed, fakeAsync, tick, flushMicrotasks } from '@angular/core/testing';
-import { AuthDirectLoginComponent } from './auth-direct-login.component';
-import { AuthService } from '../auth.service';
-import { of } from 'rxjs';
-import { Router, ActivatedRoute, convertToParamMap } from '@angular/router';
-import { SharedModule } from '@shared/shared.module';
-import { UtilsService } from '@services/utils.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { SwitcherService } from '../../switcher/switcher.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { BrowserStorageServiceMock } from '@testing/mocked.service';
-import { TestUtils } from '@testing/utils';
-import { SharedService } from '@services/shared.service';
-
-
-describe('AuthDirectLoginComponent', () => {
- let component: AuthDirectLoginComponent;
- let fixture: ComponentFixture;
- let authServiceSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
- let routeSpy: ActivatedRoute;
- let utils: UtilsService;
- let notificationSpy: jasmine.SpyObj;
- let switcherSpy: jasmine.SpyObj;
- let storageSpy: jasmine.SpyObj;
- let sharedSpy: jasmine.SpyObj;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- imports: [SharedModule],
- declarations: [AuthDirectLoginComponent],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
- providers: [
- NewRelicService,
- {
- provide: UtilsService,
- useClass: TestUtils,
- },
- {
- provide: BrowserStorageService,
- useClass: BrowserStorageServiceMock
- },
- {
- provide: SharedService,
- useValue: jasmine.createSpyObj('SharedService', ['onPageLoad', 'initWebServices']),
- },
- {
- provide: AuthService,
- useValue: jasmine.createSpyObj('AuthService', {
- 'directLogin': of(true)
- })
- },
- {
- provide: SwitcherService,
- useValue: jasmine.createSpyObj('SwitcherService', {
- 'getMyInfo': of(true),
- 'switchProgram': of(true)
- })
- },
- {
- provide: Router,
- useValue: {
- navigate: jasmine.createSpy('navigate')
- }
- },
- {
- provide: NotificationService,
- useValue: jasmine.createSpyObj('NotificationService', ['alert'])
- },
- {
- provide: ActivatedRoute,
- useValue: {
- snapshot: {
- paramMap: convertToParamMap({
- authToken: 'abc'
- })
- }
- }
- },
- ],
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(AuthDirectLoginComponent);
- component = fixture.componentInstance;
- authServiceSpy = TestBed.inject(AuthService) as jasmine.SpyObj;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- routeSpy = TestBed.inject(ActivatedRoute);
- utils = TestBed.inject(UtilsService) as jasmine.SpyObj;
- notificationSpy = TestBed.inject(NotificationService) as jasmine.SpyObj;
- switcherSpy = TestBed.inject(SwitcherService) as jasmine.SpyObj;
- storageSpy = TestBed.inject(BrowserStorageService) as jasmine.SpyObj;
- sharedSpy = TestBed.inject(SharedService) as jasmine.SpyObj;
- });
-
- beforeEach(() => {
- authServiceSpy.directLogin.and.returnValue(of({}));
- switcherSpy.getMyInfo.and.returnValue(of({}));
- switcherSpy.switchProgram.and.returnValue(of({}));
- storageSpy.get.and.returnValue([{ timeline: { id: 1 } }]);
- storageSpy.getConfig.and.returnValue({ logo: null });
- });
-
- describe('when testing ngOnInit()', () => {
- it('should pop up alert if auth token is not provided', fakeAsync(() => {
- const params = { authToken: null };
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => params[key]);
- utils.isEmpty = jasmine.createSpy('isEmpty').and.returnValue(true);
-
- tick(50);
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- expect(notificationSpy.alert.calls.count()).toBe(1);
- });
- }));
-
- it('should pop up alert if direct login service throw error', fakeAsync(() => {
- const params = { authToken: 'abc' };
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => params[key]);
- authServiceSpy.directLogin.and.throwError('');
- fixture.detectChanges();
- tick(50);
- fixture.detectChanges();
- expect(notificationSpy.alert.calls.count()).toBe(1);
-
- const button = notificationSpy.alert.calls.first().args[0].buttons[0];
- (typeof button == 'string') ? button : button.handler(true);
-
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['login']);
- }));
-
- describe('should navigate to', () => {
- let redirect;
- let switchProgram = true;
- const params = {
- authToken: 'abc',
- redirect: '',
- tl: 1,
- act: 2,
- ctxt: 3,
- asmt: 4,
- sm: 5,
- top: 6,
- };
- let tmpParams;
- let doAuthentication;
- let setReferrerCalled = false;
- beforeEach(() => {
- tmpParams = JSON.parse(JSON.stringify(params));
- doAuthentication = true;
- setReferrerCalled = false;
- });
-
- afterEach(fakeAsync(() => {
- storageSpy.getUser.and.returnValue({
- timelineId: 2
- });
- utils.find = jasmine.createSpy('find').and.returnValue([]);
- utils.isEmpty = jasmine.createSpy('isEmpty').and.returnValue(false);
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => tmpParams[key]);
- fixture.detectChanges();
- tick(50);
- fixture.detectChanges();
-
- if (doAuthentication) {
- expect(authServiceSpy.directLogin.calls.count()).toBe(1);
- expect(switcherSpy.getMyInfo.calls.count()).toBe(1);
- } else {
- expect(authServiceSpy.directLogin.calls.count()).toBe(0);
- expect(switcherSpy.getMyInfo.calls.count()).toBe(0);
- }
-
- if (switchProgram) {
- expect(switcherSpy.switchProgram.calls.count()).toBe(1);
- }
- if (setReferrerCalled) {
- expect(storageSpy.setReferrer.calls.count()).toBe(1);
- }
-
- expect(sharedSpy.initWebServices).toHaveBeenCalled();
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(redirect);
- }));
-
- it('skip authentication if auth token match', () => {
- switchProgram = false;
- redirect = ['switcher', 'switcher-program'];
- storageSpy.get.and.returnValue('abc');
- doAuthentication = false;
- });
- it('program switcher page if timeline id is not passed in', () => {
- switchProgram = false;
- redirect = ['switcher', 'switcher-program'];
- });
- it('program switcher page if timeline id is not in programs', () => {
- params.redirect = 'home';
- params.tl = 999;
- switchProgram = false;
- redirect = ['switcher', 'switcher-program'];
- });
- it('home page', () => {
- tmpParams.redirect = 'home';
- redirect = ['app', 'home'];
- });
- it('project page', () => {
- tmpParams.redirect = 'project';
- redirect = ['app', 'home'];
- });
- it('home page if activity id miss', () => {
- tmpParams.redirect = 'activity';
- tmpParams.act = null;
- redirect = ['app', 'home'];
- });
- it('activity page', () => {
- tmpParams.redirect = 'activity';
- redirect = ['app', 'activity', tmpParams.act];
- });
- it('activity-task page', () => {
- tmpParams.redirect = 'activity_task';
- redirect = ['activity-task', tmpParams.act];
- });
- it('activity-task page with referrer url', () => {
- tmpParams.redirect = 'activity_task';
- tmpParams.activity_task_referrer_url = 'https://referrer.practera.com';
- redirect = ['activity-task', tmpParams.act];
- setReferrerCalled = true;
- });
- it('home page if activity id miss', () => {
- tmpParams.redirect = 'activity_task';
- tmpParams.act = null;
- redirect = ['app', 'home'];
- });
- it('home page if activity id miss', () => {
- tmpParams.redirect = 'assessment';
- tmpParams.act = null;
- redirect = ['app', 'home'];
- });
- it('home page if context id miss', () => {
- tmpParams.redirect = 'assessment';
- tmpParams.ctxt = null;
- redirect = ['app', 'home'];
- });
- it('home page if assessment id miss', () => {
- tmpParams.redirect = 'assessment';
- tmpParams.asmt = null;
- redirect = ['app', 'home'];
- });
- it('assessment page', () => {
- utils.isMobile = jasmine.createSpy('isMobile').and.returnValues(false);
- storageSpy.singlePageAccess = false;
-
- tmpParams.redirect = 'assessment';
- redirect = [
- 'app',
- 'activity',
- tmpParams.act,
- {
- task: 'assessment',
- task_id: tmpParams.asmt,
- context_id: tmpParams.ctxt
- }
- ];
- // redirect = ['assessment', 'assessment', tmpParams.act, tmpParams.ctxt, tmpParams.asmt];
- });
-
- it('assessment page (mobile)', () => {
- utils.isMobile = jasmine.createSpy('isMobile').and.returnValues(true);
-
- tmpParams.redirect = 'assessment';
- tmpParams.sm = undefined;
- redirect = [
- 'assessment',
- 'assessment',
- tmpParams.act,
- tmpParams.ctxt,
- tmpParams.asmt,
- ];
- });
-
- it('assessment page with submission id (mobile)', () => {
- utils.isMobile = jasmine.createSpy('isMobile').and.returnValues(true);
-
- tmpParams.redirect = 'assessment';
- redirect = [
- 'assessment',
- 'assessment',
- tmpParams.act,
- tmpParams.ctxt,
- tmpParams.asmt,
- tmpParams.sm
- ];
- });
-
- it('assessment page (onePageOnly restriction)', () => {
- storageSpy.singlePageAccess = true; // singlePageRestriction
-
- tmpParams.redirect = 'assessment';
- tmpParams.sm = undefined;
- redirect = [
- 'assessment',
- 'assessment',
- tmpParams.act,
- tmpParams.ctxt,
- tmpParams.asmt,
- ];
- });
-
- it('assessment page with referrer', () => {
- utils.isMobile = jasmine.createSpy('isMobile').and.returnValues(false);
- storageSpy.singlePageAccess = false;
- tmpParams.assessment_referrer_url = 'https://referrer.practera.com';
- tmpParams.redirect = 'assessment';
- redirect = [
- 'app',
- 'activity',
- tmpParams.act,
- {
- task: 'assessment',
- task_id: tmpParams.asmt,
- context_id: tmpParams.ctxt
- }
- ];
- setReferrerCalled = true;
- });
-
- it('topic page', () => {
- utils.isMobile = jasmine.createSpy('isMobile').and.callFake(() => {
- return false;
- });
- storageSpy.singlePageAccess = false; // singlePageRestriction
-
- tmpParams.redirect = 'topic';
- redirect = [
- 'app',
- 'activity',
- tmpParams.act,
- {
- task: 'topic',
- task_id: tmpParams.top
- }
- ];
- });
-
- it('topic page (mobile)', () => {
- utils.isMobile = jasmine.createSpy('isMobile').and.callFake(() => {
- return true;
- });
-
- tmpParams.redirect = 'topic';
- redirect = [
- 'topic',
- tmpParams.act,
- tmpParams.top
- ];
- });
-
- it('topic page (onePageOnly restriction)', () => {
- storageSpy.get.and.returnValue(true); // singlePageRestriction
-
- tmpParams.redirect = 'topic';
- redirect = [
- 'topic',
- tmpParams.act,
- tmpParams.top
- ];
- });
-
- it('home page if topic id miss', () => {
- tmpParams.redirect = 'topic';
- tmpParams.top = null;
- redirect = ['app', 'home'];
- });
- it('reviews page', () => {
- tmpParams.redirect = 'reviews';
- redirect = ['app', 'reviews'];
- });
- it('home page if context id miss', () => {
- tmpParams.redirect = 'review';
- tmpParams.ctxt = null;
- redirect = ['app', 'home'];
- });
- it('home page if assessment id miss', () => {
- tmpParams.redirect = 'review';
- tmpParams.asmt = null;
- redirect = ['app', 'home'];
- });
- it('home page if submission id miss', () => {
- tmpParams.redirect = 'review';
- tmpParams.sm = null;
- redirect = ['app', 'home'];
- });
- it('review page', () => {
- tmpParams.redirect = 'review';
- redirect = ['assessment', 'review', tmpParams.ctxt, tmpParams.asmt, tmpParams.sm];
- });
- it('review page with referrer', () => {
- tmpParams.redirect = 'review';
- tmpParams.assessment_referrer_url = 'https://referrer.practera.com';
- redirect = ['assessment', 'review', tmpParams.ctxt, tmpParams.asmt, tmpParams.sm];
- setReferrerCalled = true;
- });
- it('chat page', () => {
- tmpParams.redirect = 'chat';
- redirect = ['app', 'chat'];
- });
- it('settings page', () => {
- tmpParams.redirect = 'settings';
- redirect = ['app', 'settings'];
- });
- it('settings embed page', () => {
- tmpParams.redirect = 'settings-embed';
- redirect = ['settings-embed'];
- });
- it('home page', () => {
- tmpParams.redirect = 'default';
- redirect = ['app', 'home'];
- });
-
- });
- });
-
- describe('singlePageRestriction()', () => {
- it('should cache "onePageOnly" as singlePageRestriction on localStorage', () => {
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.returnValue('true');
- const result = component.singlePageRestriction();
- expect(storageSpy.singlePageAccess).toEqual(true);
- expect(result).toEqual(true);
- });
-
- it('should not cache anything if no "onePageOnly" param provided in url', () => {
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.returnValue('anything else');
- const result = component.singlePageRestriction();
- expect(storageSpy.singlePageAccess).toEqual(false);
- expect(result).toBeFalsy();
- });
- });
-});
-
diff --git a/src/app/auth/auth-direct-login/auth-direct-login.component.ts b/src/app/auth/auth-direct-login/auth-direct-login.component.ts
deleted file mode 100644
index 2a76b2c57..000000000
--- a/src/app/auth/auth-direct-login/auth-direct-login.component.ts
+++ /dev/null
@@ -1,234 +0,0 @@
-import { Component, OnInit, NgZone } from '@angular/core';
-import { Router, ActivatedRoute } from '@angular/router';
-import { AuthService } from '../auth.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { SwitcherService } from '../../switcher/switcher.service';
-import { UtilsService } from '@services/utils.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { SharedService } from '@services/shared.service';
-
-@Component({
- selector: 'app-auth-direct-login',
- templateUrl: 'auth-direct-login.component.html',
-})
-export class AuthDirectLoginComponent implements OnInit {
- constructor(
- readonly utils: UtilsService,
- private router: Router,
- private route: ActivatedRoute,
- private authService: AuthService,
- private notificationService: NotificationService,
- private switcherService: SwitcherService,
- private storage: BrowserStorageService,
- private ngZone: NgZone,
- private newRelic: NewRelicService,
- private sharedService: SharedService
- ) {}
-
- async ngOnInit() {
- this.newRelic.setPageViewName('direct-login');
- const authToken = this.route.snapshot.paramMap.get('authToken');
- if (!authToken) {
- return this._error();
- }
-
- try {
- // skip the authentication if the same auth token has been used before
- if (this.storage.get('authToken') !== authToken) {
- await this.authService.directLogin({ authToken }).toPromise();
- await this.switcherService.getMyInfo().toPromise();
- // save the auth token to compare with future use
- this.storage.set('authToken', authToken);
- }
- this.newRelic.createTracer('Processing direct login');
- return this._redirect();
- } catch (err) {
- console.error(err);
- this._error(err);
- }
- }
-
- // force every navigation happen under radar of angular
- private navigate(direction): Promise {
- return this.ngZone.run(() => {
- this.newRelic.setCustomAttribute('redirection', direction);
- return this.router.navigate(direction);
- });
- }
-
- /**
- * Redirect user to a specific page if data is passed in, otherwise redirect to program switcher page
- *
- * @param {boolean} redirectLater
- * @returns {Promise}
- */
- private async _redirect(redirectLater?: boolean): Promise {
- const redirect = this.route.snapshot.paramMap.get('redirect');
- const activityId = +this.route.snapshot.paramMap.get('act');
- const contextId = +this.route.snapshot.paramMap.get('ctxt');
- const assessmentId = +this.route.snapshot.paramMap.get('asmt');
- const submissionId = +this.route.snapshot.paramMap.get('sm');
- const topicId = +this.route.snapshot.paramMap.get('top');
- const timelineId = +this.route.snapshot.paramMap.get('tl');
-
- // clear the cached data
- await this.utils.clearCache();
-
- if (!redirect || !timelineId) {
- // if there's no redirection or timeline id
- return this._saveOrRedirect(['switcher', 'switcher-program'], redirectLater);
- }
-
- // purpose of return_url
- // - when user switch program, he/she will be redirect to this url
- if (this.route.snapshot.paramMap.has('return_url')) {
- this.storage.setUser({
- LtiReturnUrl: this.route.snapshot.paramMap.get('return_url')
- });
- }
-
- const restrictedAccess = this.singlePageRestriction();
-
- // switch program directly if user already registered
- if (!redirectLater) {
- const program = this.utils.find(this.storage.get('programs'), value => {
- return value.timeline.id === timelineId;
- });
- if (this.utils.isEmpty(program)) {
- // if the timeline id is not found
- return this._saveOrRedirect(['switcher', 'switcher-program']);
- }
- // switch to the program
- await this.switcherService.switchProgram(program).toPromise();
- }
- let referrerUrl = '';
- switch (redirect) {
- case 'home':
- return this._saveOrRedirect(['app', 'home'], redirectLater);
- case 'project':
- return this._saveOrRedirect(['app', 'home'], redirectLater);
- case 'activity':
- if (!activityId) {
- return this._saveOrRedirect(['app', 'home'], redirectLater);
- }
- return this._saveOrRedirect(['app', 'activity', activityId], redirectLater);
- case 'activity_task':
- if (!activityId) {
- return this._saveOrRedirect(['app', 'home'], redirectLater);
- }
- referrerUrl = this.route.snapshot.paramMap.get('activity_task_referrer_url');
- if (referrerUrl) {
- // save the referrer url so that we can redirect user later
- this.storage.setReferrer({
- route: 'activity-task',
- url: referrerUrl
- });
- }
- return this._saveOrRedirect(['activity-task', activityId], redirectLater);
- case 'assessment':
- if (!activityId || !contextId || !assessmentId) {
- return this._saveOrRedirect(['app', 'home'], redirectLater);
- }
-
- referrerUrl = this.route.snapshot.paramMap.get('assessment_referrer_url');
- if (referrerUrl) {
- // save the referrer url so that we can redirect user later
- this.storage.setReferrer({
- route: 'assessment',
- url: referrerUrl
- });
- }
-
- if (this.utils.isMobile() || restrictedAccess) {
- if (submissionId) {
- return this._saveOrRedirect(['assessment', 'assessment', activityId, contextId, assessmentId, submissionId], redirectLater);
- }
- return this._saveOrRedirect(['assessment', 'assessment', activityId, contextId, assessmentId], redirectLater);
- } else {
- return this._saveOrRedirect(['app', 'activity', activityId, { task: 'assessment', task_id: assessmentId, context_id: contextId }], redirectLater);
- }
- case 'topic':
- if (!activityId || !topicId) {
- return this._saveOrRedirect(['app', 'home'], redirectLater);
- }
- if (this.utils.isMobile() || restrictedAccess) {
- return this._saveOrRedirect(['topic', activityId, topicId], redirectLater);
- } else {
- return this._saveOrRedirect(['app', 'activity', activityId, { task: 'topic', task_id: topicId }], redirectLater);
- }
- case 'reviews':
- return this._saveOrRedirect(['app', 'reviews'], redirectLater);
- case 'review':
- if (!contextId || !assessmentId || !submissionId) {
- return this._saveOrRedirect(['app', 'home'], redirectLater);
- }
- referrerUrl = this.route.snapshot.paramMap.get('assessment_referrer_url');
- if (referrerUrl) {
- // save the referrer url so that we can redirect user later
- this.storage.setReferrer({
- route: 'assessment',
- url: referrerUrl
- });
- }
- return this._saveOrRedirect(['assessment', 'review', contextId, assessmentId, submissionId], redirectLater);
- case 'chat':
- return this._saveOrRedirect(['app', 'chat'], redirectLater);
- case 'settings':
- return this._saveOrRedirect(['app', 'settings'], redirectLater);
- case 'settings-embed':
- return this._saveOrRedirect(['settings-embed'], redirectLater);
- default:
- return this._saveOrRedirect(['app', 'home'], redirectLater);
- }
- }
-
- private _saveOrRedirect(route: Array, save = false) {
- if (save) {
- return this.storage.set('directLinkRoute', route);
- }
- /**
- * Initialise Pusher.
- *
- * When user use deep link to login to app. user not going through switcher service.
- * So pusher initialise not calling after user login using using deep link.
- */
- this.sharedService.initWebServices();
- return this.navigate(route);
- }
-
- private _error(res?): Promise {
- this.newRelic.noticeError('failed direct login', res ? JSON.stringify(res) : undefined);
- if (!this.utils.isEmpty(res) && res.status === 'forbidden' && [
- 'User is not registered'
- ].includes(res.data.message)) {
- this._redirect(true);
- this.storage.set('unRegisteredDirectLink', true);
- return this.navigate(['registration', res.data.user.email, res.data.user.key]);
- }
- return this.notificationService.alert({
- message: 'Your link is invalid or expired.',
- buttons: [
- {
- text: 'OK',
- role: 'cancel',
- handler: () => {
- this.navigate(['login']);
- }
- }
- ]
- });
- }
-
- singlePageRestriction(): boolean {
- // one_page_only: display app limited to one single screen and no other view access are allowed
- const restrictedAccess: string = this.route.snapshot.paramMap.get('one_page_only');
-
- // extract single page restriction flag from url
- if (restrictedAccess) {
- this.storage.singlePageAccess = (restrictedAccess === 'true') ? true : false;
- }
-
- return this.storage.singlePageAccess;
- }
-}
diff --git a/src/app/auth/auth-forgot-password/auth-forgot-password.component.html b/src/app/auth/auth-forgot-password/auth-forgot-password.component.html
deleted file mode 100644
index 98aef6858..000000000
--- a/src/app/auth/auth-forgot-password/auth-forgot-password.component.html
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
Forgot your password? No worries, mate!
-
Enter your email and we'll send you a link that will let you pick a new password.
-
-
- Email
-
-
-
-
-
- Send Email
- Sending...
-
-
-
Remembered it?
-
Login here
-
-
-
diff --git a/src/app/auth/auth-forgot-password/auth-forgot-password.component.scss b/src/app/auth/auth-forgot-password/auth-forgot-password.component.scss
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/app/auth/auth-forgot-password/auth-forgot-password.component.spec.ts b/src/app/auth/auth-forgot-password/auth-forgot-password.component.spec.ts
deleted file mode 100644
index caeb8a46d..000000000
--- a/src/app/auth/auth-forgot-password/auth-forgot-password.component.spec.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
-import { RouterTestingModule } from '@angular/router/testing';
-import { AuthForgotPasswordComponent } from './auth-forgot-password.component';
-import { AuthService } from '../auth.service';
-import { Observable, of, pipe, throwError } from 'rxjs';
-import { SharedModule } from '@shared/shared.module';
-import { UtilsService } from '@services/utils.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { MockNewRelicService } from '@testing/mocked.service';
-import { TestUtils } from '@testing/utils';
-
-describe('AuthForgotPasswordComponent', () => {
- let component: AuthForgotPasswordComponent;
- let fixture: ComponentFixture;
- let serviceSpy: jasmine.SpyObj;
- let utils: UtilsService;
- let notificationSpy: jasmine.SpyObj;
- let storageSpy: jasmine.SpyObj;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- imports: [SharedModule, RouterTestingModule, HttpClientTestingModule],
- declarations: [AuthForgotPasswordComponent],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
- providers: [
- {
- provide: UtilsService,
- useClass: TestUtils,
- },
- {
- provide: AuthService,
- useValue: jasmine.createSpyObj('AuthService', ['forgotPassword'])
- },
- {
- provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', ['get', 'getConfig', 'getUser'])
- },
- {
- provide: NotificationService,
- useValue: jasmine.createSpyObj('NotificationService', ['alert', 'presentToast', 'popUp'])
- },
- {
- provide: NewRelicService,
- useClass: MockNewRelicService
- }
- ],
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(AuthForgotPasswordComponent);
- component = fixture.componentInstance;
- serviceSpy = TestBed.inject(AuthService) as jasmine.SpyObj;
- utils = TestBed.inject(UtilsService);
- notificationSpy = TestBed.inject(NotificationService) as jasmine.SpyObj;
- storageSpy = TestBed.inject(BrowserStorageService) as jasmine.SpyObj;
- storageSpy.getConfig.and.returnValue({ logo: null });
- });
-
- it('should create', () => {
- expect(component).toBeDefined();
- });
-
- describe('when testing send()', () => {
-
- beforeEach(() => {
- component.email = 'test@test.com';
- });
-
- it('should pop up toast message if email is empty', fakeAsync(() => {
- notificationSpy.presentToast.and.returnValue(Promise.resolve());
- component.email = '';
- component.send();
- expect(notificationSpy.presentToast.calls.count()).toBe(1);
- }));
-
- it('should pop up forgot password confirmation if success', fakeAsync(() => {
- serviceSpy.forgotPassword.and.returnValue(of({}));
- component.send();
- tick();
-
- expect(component.isSending).toBe(false);
- expect(notificationSpy.popUp.calls.count()).toBe(1);
- expect(notificationSpy.popUp.calls.first().args[1]).toEqual({ email: component.email });
- }));
-
- it('should pop up reset too frequently alert if forgot password failed', fakeAsync(() => {
- serviceSpy.forgotPassword.and.returnValue(throwError({
- data: {
- type: 'reset_too_frequently'
- }
- }));
- component.send();
- tick();
- expect(component.isSending).toBe(false);
- expect(notificationSpy.alert.calls.count()).toBe(1);
- }));
-
- it('should pop up try again alert if forgot password failed', fakeAsync(() => {
- serviceSpy.forgotPassword.and.returnValue(throwError({}));
- component.send();
- tick();
- expect(component.isSending).toBe(false);
- expect(notificationSpy.presentToast.calls.count()).toBe(1);
- }));
- });
-});
-
diff --git a/src/app/auth/auth-forgot-password/auth-forgot-password.component.ts b/src/app/auth/auth-forgot-password/auth-forgot-password.component.ts
deleted file mode 100644
index 92cf150c7..000000000
--- a/src/app/auth/auth-forgot-password/auth-forgot-password.component.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { Title } from '@angular/platform-browser';
-import { NotificationService } from '@shared/notification/notification.service';
-import { UtilsService } from '@services/utils.service';
-import { AuthService } from '../auth.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-
-@Component({
- selector: 'app-auth-forgot-password',
- templateUrl: 'auth-forgot-password.component.html',
- styleUrls: ['auth-forgot-password.component.scss']
-})
-export class AuthForgotPasswordComponent implements OnInit {
- email = '';
- // variable to control the label of the button
- isSending = false;
-
- constructor(
- private title: Title,
- private notificationService: NotificationService,
- private authService: AuthService,
- private utils: UtilsService,
- private newRelic: NewRelicService
- ) {}
-
- ngOnInit() {
- this.title.setTitle('Forgot Password - Practera');
- }
-
- async send() {
- // basic validation
- if (this.email.length < 0 || !this.email) {
- this.newRelic.actionText('email missing');
- return this.notificationService.presentToast('Please enter email');
- }
- this.isSending = true;
-
- // call API to do forgot password logic
- const nrForgotpasswordTracer = this.newRelic.createTracer('API Request: forgot-password');
- this.authService.forgotPassword(this.email).subscribe(
- res => {
- nrForgotpasswordTracer();
- this.newRelic.actionText('forgot password request sent');
- this.isSending = false;
- // show pop up message for confirmation
- return this.notificationService.popUp(
- 'forgotPasswordConfirmation', {
- email: this.email
- },
- ['/login']
- );
- },
- err => {
- nrForgotpasswordTracer();
- this.newRelic.noticeError(`Password Reset Error`, JSON.stringify(err));
- this.isSending = false;
- if (this.utils.has(err, 'data.type')) {
- // pop up if trying to reset password too frequently
- if (err.data.type === 'reset_too_frequently') {
- return this.notificationService.alert({
- message: `Please wait 2 minutes before attempting to reset your password again`,
- buttons: [
- {
- text: 'OK',
- role: 'cancel'
- }
- ],
- });
- }
- }
- return this.notificationService.presentToast('Issue occured. Please try again');
- }
- );
- }
-
-}
diff --git a/src/app/auth/auth-global-login/auth-global-login.component.html b/src/app/auth/auth-global-login/auth-global-login.component.html
deleted file mode 100644
index a0726f2bf..000000000
--- a/src/app/auth/auth-global-login/auth-global-login.component.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
We are redirecting you to the page you want to go to, please be patient
-
-
-
-
diff --git a/src/app/auth/auth-global-login/auth-global-login.component.spec.ts b/src/app/auth/auth-global-login/auth-global-login.component.spec.ts
deleted file mode 100644
index ec24e7eec..000000000
--- a/src/app/auth/auth-global-login/auth-global-login.component.spec.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
-import { AuthGlobalLoginComponent } from './auth-global-login.component';
-import { AuthService } from '../auth.service';
-import { Observable, of, pipe } from 'rxjs';
-import { Router, ActivatedRoute, convertToParamMap } from '@angular/router';
-import { SharedModule } from '@shared/shared.module';
-import { NotificationService } from '@shared/notification/notification.service';
-import { SwitcherService } from '../../switcher/switcher.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-
-describe('AuthGlobalLoginComponent', () => {
- let component: AuthGlobalLoginComponent;
- let fixture: ComponentFixture;
- let serviceSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
- let routeSpy: ActivatedRoute;
- let notificationSpy: jasmine.SpyObj;
- let switcherSpy: jasmine.SpyObj;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- imports: [SharedModule],
- declarations: [AuthGlobalLoginComponent],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
- providers: [
- NewRelicService,
- {
- provide: AuthService,
- useValue: jasmine.createSpyObj('AuthService', ['globalLogin'])
- },
- {
- provide: SwitcherService,
- useValue: jasmine.createSpyObj('SwitcherService', ['getMyInfo', 'switchProgram'])
- },
- {
- provide: Router,
- useValue: {
- navigate: jasmine.createSpy('navigate')
- }
- },
- {
- provide: NotificationService,
- useValue: jasmine.createSpyObj('NotificationService', ['alert'])
- },
- {
- provide: ActivatedRoute,
- useValue: {
- snapshot: {
- paramMap: convertToParamMap({
- apikey: 'abc'
- })
- }
- }
- },
- ],
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(AuthGlobalLoginComponent);
- component = fixture.componentInstance;
- serviceSpy = TestBed.inject(AuthService) as jasmine.SpyObj;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- routeSpy = TestBed.inject(ActivatedRoute);
- notificationSpy = TestBed.inject(NotificationService) as jasmine.SpyObj;
- switcherSpy = TestBed.inject(SwitcherService) as jasmine.SpyObj;
- });
-
- beforeEach(() => {
- serviceSpy.globalLogin.and.returnValue(of({}));
- switcherSpy.getMyInfo.and.returnValue(of({}));
- });
-
- describe('when testing ngOnInit()', () => {
- it('should pop up alert if apikey is not provided', fakeAsync(() => {
- const params = { apikey: null };
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => params[key]);
- tick(50);
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- expect(notificationSpy.alert.calls.count()).toBe(1);
- });
- }));
-
- it('should pop up alert if direct login service throw error', fakeAsync(() => {
- const params = { apikey: 'abc' };
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => params[key]);
- serviceSpy.globalLogin.and.throwError('');
- fixture.detectChanges();
- tick(50);
- fixture.detectChanges();
- expect(notificationSpy.alert.calls.count()).toBe(1);
- const button = notificationSpy.alert.calls.first().args[0].buttons[0];
- (typeof button == 'string') ? button : button.handler(true);
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['login']);
- }));
-
- describe('should navigate to', () => {
- const params = {
- apikey: 'abc'
- };
- let tmpParams;
- beforeEach(() => {
- tmpParams = JSON.parse(JSON.stringify(params));
- });
- afterEach(fakeAsync(() => {
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => tmpParams[key]);
- fixture.detectChanges();
- tick(50);
- fixture.detectChanges();
- expect(serviceSpy.globalLogin.calls.count()).toBe(1);
- expect(switcherSpy.getMyInfo.calls.count()).toBe(1);
- }));
- });
- });
-});
-
diff --git a/src/app/auth/auth-global-login/auth-global-login.component.ts b/src/app/auth/auth-global-login/auth-global-login.component.ts
deleted file mode 100644
index 122d51bcf..000000000
--- a/src/app/auth/auth-global-login/auth-global-login.component.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { Component, OnInit, NgZone } from '@angular/core';
-import { Router, ActivatedRoute } from '@angular/router';
-import { AuthService } from '../auth.service';
-import { Observable, concat } from 'rxjs';
-import { NotificationService } from '@shared/notification/notification.service';
-import { SwitcherService } from '../../switcher/switcher.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { BrowserStorageService } from '@services/storage.service';
-
-@Component({
- selector: 'app-auth-global-login',
- templateUrl: 'auth-global-login.component.html'
-})
-export class AuthGlobalLoginComponent implements OnInit {
- constructor(
- private router: Router,
- private route: ActivatedRoute,
- private authService: AuthService,
- private notificationService: NotificationService,
- private switcherService: SwitcherService,
- private ngZone: NgZone,
- private readonly newRelic: NewRelicService,
- private readonly storage: BrowserStorageService,
- ) {}
-
- async ngOnInit() {
- this.newRelic.setPageViewName('global-login');
- const apikey = this.route.snapshot.paramMap.get('apikey');
- const service = this.route.snapshot.paramMap.get('service');
- const multipleStacks = this.route.snapshot.paramMap.get('multiple');
- if (!apikey) {
- return this._error();
- }
- try {
- await this.authService.globalLogin({ apikey, service }).toPromise();
- await this.switcherService.getMyInfo().toPromise();
- this.newRelic.createTracer('Processing global login');
- if (multipleStacks) {
- this.storage.set('hasMultipleStacks', true);
- }
- return this.navigate(['switcher', 'switcher-program']);
- } catch (err) {
- this._error(err);
- }
- }
-
- // force every navigation happen under radar of angular
- private navigate(direction): Promise {
- return this.ngZone.run(() => {
- this.newRelic.setCustomAttribute('redirection', direction);
- return this.router.navigate(direction);
- });
- }
-
- private _error(res?): Promise {
- this.newRelic.noticeError('failed global login', res ? JSON.stringify(res) : undefined);
- return this.notificationService.alert({
- message: 'Your link is invalid or expired.',
- buttons: [
- {
- text: 'OK',
- role: 'cancel',
- handler: () => {
- this.navigate(['login']);
- }
- }
- ]
- });
- }
-
-}
diff --git a/src/app/auth/auth-login/auth-login.component.html b/src/app/auth/auth-login/auth-login.component.html
deleted file mode 100644
index 1f47ba58e..000000000
--- a/src/app/auth/auth-login/auth-login.component.html
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/src/app/auth/auth-login/auth-login.component.scss b/src/app/auth/auth-login/auth-login.component.scss
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/app/auth/auth-login/auth-login.component.spec.ts b/src/app/auth/auth-login/auth-login.component.spec.ts
deleted file mode 100644
index 96ec2b5c5..000000000
--- a/src/app/auth/auth-login/auth-login.component.spec.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
-import { RouterTestingModule } from '@angular/router/testing';
-import { AuthLoginComponent } from './auth-login.component';
-import { AuthService } from '../auth.service';
-import { SwitcherService } from '../../switcher/switcher.service';
-import { Router } from '@angular/router';
-import { Observable, of, pipe, throwError } from 'rxjs';
-import { SharedModule } from '@shared/shared.module';
-import { NotificationService } from '@shared/notification/notification.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { ReactiveFormsModule } from '@angular/forms';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
-import { MockNewRelicService } from '@testing/mocked.service';
-import { UtilsService } from '@app/services/utils.service';
-import { TestUtils } from '@testing/utils';
-
-describe('AuthLoginComponent', () => {
- let component: AuthLoginComponent;
- let fixture: ComponentFixture;
- let serviceSpy: jasmine.SpyObj;
- let notificationSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
- let switcherServiceSpy: jasmine.SpyObj;
- let newRelicSpy: jasmine.SpyObj;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [SharedModule, RouterTestingModule, ReactiveFormsModule, HttpClientTestingModule],
- declarations: [ AuthLoginComponent ],
- schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
- providers: [
- {
- provide: UtilsService,
- useClass: TestUtils
- },
- {
- provide: AuthService,
- useValue: jasmine.createSpyObj('AuthService', ['login'])
- },
- {
- provide: NotificationService,
- useValue: jasmine.createSpyObj('NotificationService', ['alert'])
- },
- {
- provide: Router,
- useValue: {
- navigate: jasmine.createSpy('navigate'),
- events: of(),
- routerState: {root: {}}
- }
- },
- {
- provide: NewRelicService,
- useClass: MockNewRelicService
- },
- {
- provide: SwitcherService,
- useValue: jasmine.createSpyObj('SwitcherService', ['switchProgramAndNavigate'])
- }
- ],
- }).compileComponents();
- });
-
- beforeEach(() => {
- fixture = TestBed.createComponent(AuthLoginComponent);
- component = fixture.componentInstance;
- serviceSpy = TestBed.inject(AuthService) as jasmine.SpyObj;
- notificationSpy = TestBed.inject(NotificationService) as jasmine.SpyObj;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- switcherServiceSpy = TestBed.inject(SwitcherService) as jasmine.SpyObj;
- newRelicSpy = TestBed.inject(NewRelicService) as jasmine.SpyObj;
- });
-
- it('should create', () => {
- expect(component).toBeDefined();
- });
-
- describe('when testing login()', () => {
- it('should pop up alert if email is empty', () => {
- component.loginForm.setValue({email: '', password: 'abc'});
- notificationSpy.alert.and.returnValue(Promise.resolve());
- component.login();
- expect(notificationSpy.alert.calls.count()).toBe(1);
-
- const button = notificationSpy.alert.calls.first().args[0].buttons[0];
- (typeof button == 'string') ? button : button.handler(true);
-
- expect(component.isLoggingIn).toBe(false);
- });
-
- it('should navigate to dashboard if have one program after successfully login', fakeAsync(() => {
- switcherServiceSpy.switchProgramAndNavigate.and.returnValue(Promise.resolve(['app', 'home']));
- component.loginForm.setValue({email: 'test@test.com', password: 'abc'});
- serviceSpy.login.and.returnValue(of({}));
- component.login();
- tick();
- expect(serviceSpy.login.calls.count()).toBe(1);
- expect(switcherServiceSpy.switchProgramAndNavigate.calls.count()).toBe(1);
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['app', 'home']);
- }));
-
- it('should pop up password compromised alert if login failed', fakeAsync(() => {
- component.loginForm.setValue({email: 'test@test.com', password: 'abc'});
- serviceSpy.login.and.returnValue(throwError({data: {type: 'password_compromised'}}));
- component.login();
- tick();
- expect(serviceSpy.login.calls.count()).toBe(1);
- expect(component.isLoggingIn).toBe(false);
- expect(notificationSpy.alert.calls.count()).toBe(1);
- expect(notificationSpy.alert.calls.first().args[0].message).toContain('insecure passwords');
- }));
-
- it(`should pop up 'incorrect' alert if login failed`, fakeAsync(() => {
- component.loginForm.setValue({email: 'test@test.com', password: 'abc'});
- serviceSpy.login.and.returnValue(throwError({}));
- component.login();
- tick();
- expect(serviceSpy.login.calls.count()).toBe(1);
- expect(component.isLoggingIn).toBe(true);
- expect(notificationSpy.alert.calls.count()).toBe(1);
- expect(notificationSpy.alert.calls.first().args[0].message).toContain('password is incorrect');
-
- const button = notificationSpy.alert.calls.first().args[0].buttons[0];
- (typeof button == 'string') ? button : button.handler(true);
-
- expect(component.isLoggingIn).toBe(false);
- }));
- });
-});
-
diff --git a/src/app/auth/auth-login/auth-login.component.ts b/src/app/auth/auth-login/auth-login.component.ts
deleted file mode 100644
index c31f62c0a..000000000
--- a/src/app/auth/auth-login/auth-login.component.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { Router } from '@angular/router';
-import { Title } from '@angular/platform-browser';
-import { AuthService } from '../auth.service';
-import { Validators, FormGroup, FormControl } from '@angular/forms';
-import { NotificationService } from '@shared/notification/notification.service';
-import { UtilsService } from '@services/utils.service';
-import { SwitcherService } from '../../switcher/switcher.service';
-import { PusherService } from '@shared/pusher/pusher.service';
-
-@Component({
- selector: 'app-auth-login',
- templateUrl: 'auth-login.component.html',
- styleUrls: ['auth-login.component.scss']
-})
-export class AuthLoginComponent implements OnInit {
- loginForm = new FormGroup({
- email: new FormControl('', [Validators.required]),
- password: new FormControl('', [Validators.required]),
- });
- isLoggingIn = false;
- showPassword = false;
-
- constructor(
- private router: Router,
- private title: Title,
- private authService: AuthService,
- private notificationService: NotificationService,
- private utils: UtilsService,
- private switcherService: SwitcherService,
- private pusherService: PusherService,
- ) {}
-
- ngOnInit() {
- this.title.setTitle('Login - Practera');
- }
-
- login() {
- if (this.utils.isEmpty(this.loginForm.value.email) || this.utils.isEmpty(this.loginForm.value.password)) {
- this.notificationService.alert({
- message: 'Your email or password is empty, please fill them in.',
- buttons: [
- {
- text: 'OK',
- role: 'cancel',
- handler: () => {
- this.isLoggingIn = false;
- return;
- }
- }
- ]
- });
- return;
- }
- this.isLoggingIn = true;
-
- return this.authService.login({
- email: this.loginForm.value.email,
- password: this.loginForm.value.password,
- }).subscribe(
- res => {
- return this._handleNavigation(res.programs);
- },
- err => {
- // notify user about weak password
- if (this.utils.has(err, 'data.type')) {
- if (err.data.type === 'password_compromised') {
- this.isLoggingIn = false;
- return this.notificationService.alert({
- message: `We’ve checked this password against a global database of insecure passwords and your password was on it.
- We have sent you an email with a link to reset your password.
- You can learn more about how we check that database `,
- buttons: [
- {
- text: 'OK',
- role: 'cancel'
- }
- ],
- });
- }
- }
-
- // credential issue
- this.notificationService.alert({
- message: 'Your email or password is incorrect, please try again.',
- buttons: [
- {
- text: 'OK',
- role: 'cancel',
- handler: () => {
- this.isLoggingIn = false;
- return;
- },
- },
- ],
- });
- }
- );
- }
-
- private async _handleNavigation(programs) {
- const route = await this.switcherService.switchProgramAndNavigate(programs);
- this.isLoggingIn = false;
- return this.router.navigate(route);
- }
-}
diff --git a/src/app/auth/auth-logout/auth-logout.component.spec.ts b/src/app/auth/auth-logout/auth-logout.component.spec.ts
deleted file mode 100644
index a1b8c8811..000000000
--- a/src/app/auth/auth-logout/auth-logout.component.spec.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { AuthLogoutComponent } from './auth-logout.component';
-import { async, ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
-import { AuthService } from '../auth.service';
-import { Router, ActivatedRoute } from '@angular/router';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { MockNewRelicService } from '@testing/mocked.service';
-import { Observable, of } from 'rxjs';
-import { RouterTestingModule } from '@angular/router/testing';
-import { doesNotReject } from 'assert';
-import { ActivatedRouteStub } from '@testing/activated-route-stub';
-
-describe('AuthLogoutComponent', () => {
- let component: AuthLogoutComponent;
- let fixture: ComponentFixture;
- let authSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
- let routeSpy: ActivatedRoute;
- let newRelicSpy: jasmine.SpyObj;
-
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [AuthLogoutComponent],
- imports: [RouterTestingModule],
- providers: [
-
- {
- provide: AuthService,
- useValue: jasmine.createSpyObj('AuthService', ['logout'])
- },
-
- {
- provide: NewRelicService,
- useClass: MockNewRelicService
- },
- {
- provide: ActivatedRoute,
- useValue: new ActivatedRouteStub({ t: 1 })
- }
- ],
- }).compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(AuthLogoutComponent);
- component = fixture.componentInstance;
- authSpy = TestBed.inject(AuthService) as jasmine.SpyObj;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- routeSpy = TestBed.inject(ActivatedRoute);
- newRelicSpy = TestBed.inject(NewRelicService) as jasmine.SpyObj;
- });
-
-
- it('should create', () => {
- expect(component).toBeDefined();
- });
-
- it('when testing onEnter() and there is no route param should call auth Service logout', fakeAsync(() => {
- const params = of({ t: 1 });
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => params[key]);
- fixture.detectChanges();
- component.onEnter();
- expect(newRelicSpy.setPageViewName).toHaveBeenCalledWith('logout');
- authSpy.logout.and.returnValue(Promise.resolve(true));
- expect(authSpy.logout.calls.count()).toBe(1);
- }));
-});
diff --git a/src/app/auth/auth-logout/auth-logout.component.ts b/src/app/auth/auth-logout/auth-logout.component.ts
deleted file mode 100644
index 79cbd7168..000000000
--- a/src/app/auth/auth-logout/auth-logout.component.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Component } from '@angular/core';
-import { AuthService } from '../auth.service';
-import { Router, ActivatedRoute } from '@angular/router';
-import { RouterEnter } from '@services/router-enter.service';
-import { switchMap } from 'rxjs/operators';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-
-@Component({
- selector: 'app-auth-logout',
- template: '',
-})
-export class AuthLogoutComponent extends RouterEnter {
- routeUrl = '/logout';
- constructor(
- public router: Router,
- private authService: AuthService,
- private route: ActivatedRoute,
- private newRelic: NewRelicService
- ) {
- super(router);
- }
-
- onEnter() {
- this.newRelic.setPageViewName('logout');
- this.route.params.subscribe(params => {
- if (params && params.t) {
- return this.authService.logout(params);
- }
- return this.authService.logout();
- });
- }
-
-}
diff --git a/src/app/auth/auth-registration/auth-registration.component.html b/src/app/auth/auth-registration/auth-registration.component.html
deleted file mode 100644
index c818ffc1e..000000000
--- a/src/app/auth/auth-registration/auth-registration.component.html
+++ /dev/null
@@ -1,134 +0,0 @@
-
-
-
-
-
-
-
-
- Your Login Email Address
- {{user.email}}
-
-
- Your Mobile Number
- {{user.contact}}
-
-
-
-
-
-
-
Welcome {{user.email}}.
-
-
-
-
-
-
- I agree to the Terms and Conditions
-
-
-
-
-
-
-
-
Optionally, create an account password as an additional future login option.
-
-
-
-
- Password
-
-
-
-
-
-
-
-
-
-
-
- {{error}}
-
-
CONTINUE
-
-
-
-
-
-
diff --git a/src/app/auth/auth-registration/auth-registration.component.scss b/src/app/auth/auth-registration/auth-registration.component.scss
deleted file mode 100644
index e0293a7c3..000000000
--- a/src/app/auth/auth-registration/auth-registration.component.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-.list-terms {
- text-align: left;
- background: transparent;
- margin: auto;
- margin-top: 16px;
- max-width: 480px;
-
- ion-checkbox {
- vertical-align: middle;
- margin-right: 16px;
- }
-
- a {
- cursor: pointer;
- }
-}
-
-.title {
- .headline-6 {
- font-size: 19px !important;
- }
-}
-
-.div-after-logo {
- &.directlink {
- margin: 0 16px 0px !important;
-
- .continue-btn {
- max-width: 470px;
- margin: auto;
- }
- }
-}
-
-.tc-container{
- margin-bottom: 65px !important;
-}
-.password-container {
- margin-bottom: 50px !important;
-}
diff --git a/src/app/auth/auth-registration/auth-registration.component.spec.ts b/src/app/auth/auth-registration/auth-registration.component.spec.ts
deleted file mode 100644
index 62250b345..000000000
--- a/src/app/auth/auth-registration/auth-registration.component.spec.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { async, ComponentFixture, fakeAsync, TestBed, flushMicrotasks } from '@angular/core/testing';
-import { ReactiveFormsModule } from '@angular/forms';
-import { ActivatedRoute, convertToParamMap } from '@angular/router';
-import { BrowserStorageService } from '@app/services/storage.service';
-import { UtilsService } from '@app/services/utils.service';
-import { NewRelicService } from '@app/shared/new-relic/new-relic.service';
-import { NotificationService } from '@app/shared/notification/notification.service';
-import { SharedModule } from '@app/shared/shared.module';
-import { SwitcherService } from '@app/switcher/switcher.service';
-import { ModalController } from '@ionic/angular';
-import { TestUtils } from '@testing/utils';
-import { of } from 'rxjs';
-import { AuthService } from '../auth.service';
-
-import { AuthRegistrationComponent } from './auth-registration.component';
-
-describe('AuthRegistrationComponent', () => {
- let component: AuthRegistrationComponent;
- let fixture: ComponentFixture;
- let authServiceSpy: jasmine.SpyObj;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- imports: [ SharedModule, ReactiveFormsModule ],
- declarations: [ AuthRegistrationComponent ],
- schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
- providers: [
- {
- provide: ActivatedRoute,
- useValue: {
- queryParamMap: of(true),
- snapshot: {
- paramMap: convertToParamMap({
- email: 'test@practera.com',
- key: 'abcdefg',
- })
- }
- }
- },
- {
- provide: AuthService,
- useValue: {
- saveRegistration: () => of(true),
- verifyRegistration: () => of(true),
- checkDomain: () => of(true),
- deeplink: 'some value',
- login: () => of(true),
- }
- },
- {
- provide: UtilsService,
- useClass: TestUtils,
- },
- {
- provide: NotificationService,
- useValue: jasmine.createSpyObj('NotificationService', ['popUp', 'alert']),
- },
- {
- provide: ModalController,
- useValue: jasmine.createSpyObj('ModalController', ['create'])
- },
- BrowserStorageService,
- NewRelicService,
- {
- provide: SwitcherService,
- useValue: {
- switchProgramAndNavigate: () => Promise.resolve(true)
- }
- },
- ]
- })
- .compileComponents();
- authServiceSpy = TestBed.inject(AuthService) as jasmine.SpyObj;
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(AuthRegistrationComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-
- it('should register', fakeAsync(() => {
- component.unRegisteredDirectLink = true;
- component.isAgreed = true;
- component.password = 'dummy_password';
- component.register();
-
- flushMicrotasks();
-
- expect(authServiceSpy.deeplink).toBeNull();
- }));
-});
diff --git a/src/app/auth/auth-registration/auth-registration.component.ts b/src/app/auth/auth-registration/auth-registration.component.ts
deleted file mode 100644
index b0203d538..000000000
--- a/src/app/auth/auth-registration/auth-registration.component.ts
+++ /dev/null
@@ -1,334 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { Router, ActivatedRoute, ParamMap } from '@angular/router';
-import { Title } from '@angular/platform-browser';
-import { UtilsService } from '@services/utils.service';
-import { NotificationService } from '@shared/notification/notification.service';
-import { Md5 } from 'ts-md5/dist/md5';
-import {
- Validators,
- FormControl,
- FormGroup,
-} from '@angular/forms';
-import { ModalController } from '@ionic/angular';
-
-import { AuthService } from '../auth.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-import { SwitcherService } from '../../switcher/switcher.service';
-import { TermsConditionsPreviewComponent } from '../terms-conditions-preview/terms-conditions-preview.component';
-
-@Component({
- selector: 'app-auth-registration',
- templateUrl: './auth-registration.component.html',
- styleUrls: ['./auth-registration.component.scss']
-})
-export class AuthRegistrationComponent implements OnInit {
- password: string;
- confirmPassword: string;
- isAgreed = false;
- registerationForm: FormGroup;
- hide_password = false;
- user: any = {
- email: null,
- key: null,
- contact: null,
- id: null
- };
- domain = window.location.hostname;
- // validation errors array
- errors: Array = [];
- showPassword = false;
- // for unregisterd users using direct link
- unRegisteredDirectLink = false;
-
- constructor(
- private route: ActivatedRoute,
- private title: Title,
- private authService: AuthService,
- private utils: UtilsService,
- private storage: BrowserStorageService,
- private notificationService: NotificationService,
- private newRelic: NewRelicService,
- private switcherService: SwitcherService,
- private modalController: ModalController,
- ) {
- this.initForm();
- }
-
- ngOnInit() {
- this.title.setTitle('Registration - Practera');
- this.domain =
- this.domain.indexOf('127.0.0.1') !== -1 ||
- this.domain.indexOf('localhost') !== -1
- ? 'appdev.practera.com'
- : this.domain;
- this.validateQueryParams();
- this.newRelic.setPageViewName('registration');
- if (this.storage.get('unRegisteredDirectLink')) {
- this.unRegisteredDirectLink = true;
- }
- }
-
- initForm() {
- this.registerationForm = new FormGroup({
- email: new FormControl('', [Validators.email]),
- password: new FormControl('', [
- Validators.required,
- Validators.minLength(8)
- ]),
- confirmPassword: new FormControl('', [Validators.required])
- });
- }
-
- validateQueryParams() {
- let redirect = [];
- redirect = ['login'];
-
- const verifyRegistration = this.newRelic.createTracer('verify registration');
- const getConfig = this.newRelic.createTracer('retrieve configurations');
-
- // access query params
- this.route.queryParamMap.subscribe(queryParams => {
- this.user.email = this.route.snapshot.paramMap.get('email');
- this.user.key = this.route.snapshot.paramMap.get('key');
- if (this.user.email && this.user.key) {
- // check is Url valid or not.
- this.authService.verifyRegistration({
- email: this.user.email,
- key: this.user.key
- }).subscribe(
- response => {
- verifyRegistration();
-
- if (response) {
- const user = response.data.User;
- // Setting user data after registration verified.
- this.user.contact = (user || {}).contact_number || null;
- this.user.id = user.id;
-
- // Update storage data
- this.storage.setUser({
- contactNumber: this.user.contact,
- email: this.user.email
- });
-
- // get app configaration
- this.authService.checkDomain({
- domain: this.domain
- }).subscribe(
- res => {
- getConfig();
-
- let data = (res.data || {}).data;
- data = this.utils.find(data, function(datum) {
- return (
- datum.config && datum.config.auth_via_contact_number
- );
- });
- if (data && data.config) {
- if (data.config.auth_via_contact_number === true) {
- this.hide_password = true;
- this.user.password = this.autoGeneratePassword();
- this.confirmPassword = this.user.password;
- }
- }
- },
- err => {
- getConfig();
- this.newRelic.noticeError('Get configurations failed', JSON.stringify(err));
- this.showPopupMessages('shortMessage', 'Registration link invalid!', redirect);
- }
- );
- }
- },
- error => {
- verifyRegistration();
- this.newRelic.noticeError('verification failed', JSON.stringify(error));
- this.showPopupMessages('shortMessage', 'Registration link invalid!', redirect);
- }
- );
- } else {
- this.showPopupMessages('shortMessage', 'Registration link invalid!', redirect);
- }
- });
- }
-
- private autoGeneratePassword() {
- const text = Md5.hashStr('').toString();
- const autoPass = text.substr(0, 8);
- return autoPass;
- }
-
- openLink() {
- const fileURL = 'https://images.practera.com/terms_and_conditions/practera_default_terms_conditions_july2018.pdf';
- this.newRelic.actionText(`opened ${fileURL}`);
- window.open(fileURL, '_system');
- }
-
- register() {
- if (this.validateRegistration()) {
- const nrRegisterTracer = this.newRelic.createTracer('registering');
- this.newRelic.actionText('Validated registration');
- if (this.unRegisteredDirectLink) {
- this._setupPassword();
- }
- this.authService
- .saveRegistration({
- password: this.confirmPassword,
- user_id: this.user.id,
- key: this.user.key
- })
- .subscribe(
- response => {
- nrRegisterTracer();
- const nrAutoLoginTracer = this.newRelic.createTracer('auto login');
-
- // clear up "memory deepLink" to avoid re-do registration
- this.authService.deeplink = null;
-
- this.authService.login({
- email: this.user.email,
- password: this.confirmPassword
- })
- .subscribe(
- async res => {
- nrAutoLoginTracer();
- this.storage.remove('unRegisteredDirectLink');
- const route = await this.switcherService.switchProgramAndNavigate(res.programs);
- this.showPopupMessages('shortMessage', 'Registration success!', route);
- },
- err => {
- nrAutoLoginTracer();
- this.newRelic.noticeError('auto login failed', JSON.stringify(err));
- this.showPopupMessages('shortMessage', 'Registration not complete!');
- }
- );
- },
- error => {
- this.newRelic.noticeError('registration failed', JSON.stringify(error));
-
- if (this.utils.has(error, 'data.type')) {
- if (error.data.type === 'password_compromised') {
- return this.notificationService.alert({
- message: `We’ve checked this password against a global database of insecure passwords and your password was on it.
- Please try again.
- You can learn more about how we check that database `,
- buttons: [
- {
- text: 'OK',
- role: 'cancel'
- }
- ],
- });
- }
- }
- this.showPopupMessages('shortMessage', 'Registration not complete!');
- }
- );
- }
- }
-
- removeErrorMessages() {
- this.errors = [];
- }
-
- validateRegistration() {
- let isValid = true;
- this.errors = [];
- if (this.unRegisteredDirectLink) {
- if (!this.isAgreed) {
- this.errors.push('You need to agree with terms and Conditions.');
- isValid = false;
- return isValid;
- } else {
- return isValid;
- }
- }
- if (this.hide_password) {
- if (!this.isAgreed) {
- this.errors.push('You need to agree with terms and Conditions.');
- isValid = false;
- return isValid;
- } else {
- return isValid;
- }
- } else if (this.registerationForm.valid) {
- const pass = this.registerationForm.controls.password.value;
- const confirmPass = this.registerationForm.controls.confirmPassword.value;
- if (pass !== confirmPass) {
- this.errors.push('Your passwords don\'t match.');
- isValid = false;
- return isValid;
- } else if (!this.isAgreed) {
- this.errors.push('You need to agree with terms and Conditions.');
- isValid = false;
- return isValid;
- } else {
- return isValid;
- }
- } else {
- for (const conrtoller in this.registerationForm.controls) {
- if (this.registerationForm.controls[conrtoller].errors) {
- isValid = false;
- for (const key in this.registerationForm.controls[conrtoller].errors) {
- if (
- this.registerationForm.controls[conrtoller].errors.hasOwnProperty(
- key
- )
- ) {
- switch (key) {
- case 'required':
- this.errors.push('Please fill in your password');
- break;
- case 'minlength':
- this.errors.push(
- 'Your password needs to be more than 8 characters.'
- );
- break;
- default:
- this.errors.push(this.registerationForm.controls.errors[key]);
- }
- return;
- }
- }
- }
- }
- return isValid;
- }
- }
-
- private showPopupMessages(type: string, message: string, redirect?: any) {
- this.notificationService
- .popUp(
- type,
- {
- message: message
- },
- redirect ? redirect : false
- );
- }
-
- private _setupPassword() {
- if (this.password) {
- this.user.password = this.password;
- } else {
- this.user.password = this.autoGeneratePassword();
- }
- this.confirmPassword = this.user.password;
- }
-
- async termsAndConditionsPopup() {
- const modal = await this.modalController.create({
- component: TermsConditionsPreviewComponent,
- swipeToClose: false,
- backdropDismiss: false
- });
- await modal.present();
- modal.onWillDismiss().then((modalData) => {
- if (modalData.data && (modalData.data.isAgreed)) {
- this.isAgreed = modalData.data.isAgreed;
- }
- });
- }
-
-}
diff --git a/src/app/auth/auth-reset-password/auth-reset-password.component.html b/src/app/auth/auth-reset-password/auth-reset-password.component.html
deleted file mode 100644
index d06d00291..000000000
--- a/src/app/auth/auth-reset-password/auth-reset-password.component.html
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
-
Reset your password
-
Please enter a new password for your account.
-
-
-
-
Remembered it?
-
Login here
-
-
-
-
diff --git a/src/app/auth/auth-reset-password/auth-reset-password.component.scss b/src/app/auth/auth-reset-password/auth-reset-password.component.scss
deleted file mode 100644
index d39ac0bd4..000000000
--- a/src/app/auth/auth-reset-password/auth-reset-password.component.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-.invalid-field {
- color: var(--ion-color-danger);
- border-bottom: 1px solid var(--ion-color-danger);
-}
diff --git a/src/app/auth/auth-reset-password/auth-reset-password.component.spec.ts b/src/app/auth/auth-reset-password/auth-reset-password.component.spec.ts
deleted file mode 100644
index deef49596..000000000
--- a/src/app/auth/auth-reset-password/auth-reset-password.component.spec.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { APP_BASE_HREF, Location, LocationStrategy, PathLocationStrategy } from '@angular/common';
-import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
-import { AuthResetPasswordComponent } from './auth-reset-password.component';
-import { AuthService } from '../auth.service';
-import { Observable, of, pipe, throwError } from 'rxjs';
-import { SharedModule } from '@shared/shared.module';
-import { Router, ActivatedRoute, UrlSerializer } from '@angular/router';
-import { ActivatedRouteStub } from '@testing/activated-route-stub';
-import { NotificationService } from '@shared/notification/notification.service';
-import { BrowserStorageService } from '@services/storage.service';
-import { ReactiveFormsModule } from '@angular/forms';
-import { BrowserStorageServiceMock } from '@testing/mocked.service';
-import { UtilsService } from '@app/services/utils.service';
-import { TestUtils } from '@testing/utils';
-
-describe('AuthResetPasswordComponent', () => {
- let component: AuthResetPasswordComponent;
- let fixture: ComponentFixture;
- let authServiceSpy: jasmine.SpyObj;
- let notificationSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
- let routeSpy: ActivatedRoute;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [SharedModule, ReactiveFormsModule],
- declarations: [AuthResetPasswordComponent],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
- providers: [
- { provide: APP_BASE_HREF, useValue: '/' },
- Location,
- {
- provide: LocationStrategy,
- useClass: PathLocationStrategy
- },
- UrlSerializer,
- {
- provide: AuthService,
- useValue: jasmine.createSpyObj('AuthService', ['resetPassword', 'verifyResetPassword'])
- },
- {
- provide: BrowserStorageService,
- useClass: BrowserStorageServiceMock
- },
- {
- provide: NotificationService,
- useValue: jasmine.createSpyObj('NotificationService', ['alert', 'presentToast', 'popUp'])
- },
- {
- provide: Router,
- useValue: {
- navigate: jasmine.createSpy('navigate'),
- events: of()
- }
- },
- {
- provide: ActivatedRoute,
- useValue: new ActivatedRouteStub({ apikey: 'abc' })
- },
- {
- provide: UtilsService,
- useClass: TestUtils,
- }
- ],
- }).compileComponents();
- });
-
- beforeEach(() => {
- fixture = TestBed.createComponent(AuthResetPasswordComponent);
- component = fixture.componentInstance;
- authServiceSpy = TestBed.inject(AuthService) as jasmine.SpyObj;
- notificationSpy = TestBed.inject(NotificationService) as jasmine.SpyObj;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- routeSpy = TestBed.inject(ActivatedRoute);
- authServiceSpy.resetPassword.and.returnValue(of({}));
- authServiceSpy.verifyResetPassword.and.returnValue(of({}));
- });
-
- it('should create', () => {
- expect(component).toBeDefined();
- });
-
- describe('when testing ngOnInit()', () => {
- it('should pop up alert and redirect if no key or email provided', () => {
- const params = {
- key: null,
- email: 'abc@test.com'
- };
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => params[key]);
- fixture.detectChanges();
- expect(notificationSpy.alert.calls.count()).toBe(1);
- expect(notificationSpy.alert.calls.first().args[0].message).toContain('Invalid');
-
- const button = notificationSpy.alert.calls.first().args[0].buttons[0];
- (typeof button == 'string') ? button : button.handler(true);
-
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['login']);
- });
-
- it('should verify success', () => {
- const params = {
- key: 'key-is-available',
- email: 'abc@test.com'
- };
- routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => params[key]);
- // component.ngOnInit();
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- expect(component.verifySuccess).toBe(true);
- });
- });
-
- it('should pop up alert and redirect if verify resetpassword failed', () => {
- authServiceSpy.verifyResetPassword.and.returnValue(throwError(''));
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- expect(notificationSpy.alert.calls.count()).toBe(1);
- expect(notificationSpy.alert.calls.first().args[0].message).toContain('Invalid');
-
- const button = notificationSpy.alert.calls.first().args[0].buttons[0];
- (typeof button == 'string') ? button : button.handler(true);
-
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['login']);
- });
- });
- });
-
- describe('when testing resetPassword()', () => {
- beforeEach(() => {
- component.key = 'abc';
- component.email = 'abc@test.com',
- component.resetPasswordForm.setValue({ email: 'abc@test.com', password: 'aaa', confirmPassword: 'aaa' });
- });
- it('should pop up success and redirect', () => {
- component.resetPassword();
- expect(notificationSpy.alert.calls.count()).toBe(1);
- expect(notificationSpy.alert.calls.first().args[0].message).toContain('successfully');
-
- const button = notificationSpy.alert.calls.first().args[0].buttons[0];
- (typeof button == 'string') ? button : button.handler(true);
-
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['login']);
- });
-
- it('should pop up alert if password compromised', fakeAsync(() => {
- authServiceSpy.resetPassword.and.returnValue(throwError({
- data: { type: 'password_compromised' }
- }));
- component.resetPassword();
- tick();
- expect(notificationSpy.alert.calls.count()).toBe(1);
- expect(notificationSpy.alert.calls.first().args[0].message).toContain('insecure passwords');
- }));
-
- it('should pop up alert if reset password failed', () => {
- authServiceSpy.resetPassword.and.returnValue(throwError(''));
- component.resetPassword();
- expect(notificationSpy.presentToast.calls.count()).toBe(1);
- });
- });
- describe('when testing checkPasswordMatching()', () => {
- it('should return true if password match', () => {
- component.resetPasswordForm.setValue({ email: 'abc@test.com', password: 'aaa', confirmPassword: 'aaa' });
- expect(component.checkPasswordMatching(component.resetPasswordForm)).toBe(null);
- });
- it('should return false if password not match', () => {
- component.resetPasswordForm.setValue({ email: 'abc@test.com', password: 'aaa', confirmPassword: 'aaaa' });
- expect(component.checkPasswordMatching(component.resetPasswordForm)).toEqual({ notMatching: true });
- });
- });
-});
-
diff --git a/src/app/auth/auth-reset-password/auth-reset-password.component.ts b/src/app/auth/auth-reset-password/auth-reset-password.component.ts
deleted file mode 100644
index c8da87fb3..000000000
--- a/src/app/auth/auth-reset-password/auth-reset-password.component.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { Router, ActivatedRoute } from '@angular/router';
-import { Title } from '@angular/platform-browser';
-import { Validators, FormGroup, FormControl } from '@angular/forms';
-import { NotificationService } from '../../shared/notification/notification.service';
-import { AuthService } from '../auth.service';
-import { UtilsService } from '@services/utils.service';
-import { NewRelicService } from '@shared/new-relic/new-relic.service';
-
-@Component({
- selector: 'app-auth-reset-password',
- templateUrl: './auth-reset-password.component.html',
- styleUrls: ['./auth-reset-password.component.scss']
-})
-export class AuthResetPasswordComponent implements OnInit {
- email: string;
- key: string;
-
- verifySuccess = false;
- isResetting = false;
- showPassword = false;
-
- resetPasswordForm: FormGroup;
-
- constructor(
- private route: ActivatedRoute,
- private router: Router,
- private title: Title,
- private notificationService: NotificationService,
- private authService: AuthService,
- private utils: UtilsService,
- private newRelic: NewRelicService
- ) {
- this.resetPasswordForm = new FormGroup(
- {
- email: new FormControl(
- {
- value: this.email,
- disabled: true,
- },
- [Validators.email]
- ),
- password: new FormControl('', [Validators.required]),
- confirmPassword: new FormControl(''),
- },
- { validators: this.checkPasswordMatching }
- );
- }
-
- ngOnInit() {
- this.title.setTitle('Reset Password - Practera');
- this.key = this.route.snapshot.paramMap.get('key');
- this.email = this.route.snapshot.paramMap.get('email');
-
- if (!this.key || !this.email) {
- return this._notifyAndRedirect('Invalid reset password link');
- }
-
- const nrVerifyResetTracer = this.newRelic.createTracer('verify reset password');
-
- // Call API to verify that key and email parameters from reset password URL are valid
- this.authService.verifyResetPassword({ key: this.key, email: this.email }).subscribe(
- res => {
- nrVerifyResetTracer();
- // verification of key and email is successfuly.
- this.verifySuccess = true;
- },
- err => {
- nrVerifyResetTracer();
- this.newRelic.noticeError('verify reset', JSON.stringify(err));
- return this._notifyAndRedirect('Invalid reset password link');
- }
- );
- }
-
- resetPassword() {
- const data = {
- key: this.key,
- email: this.email,
- password: this.resetPasswordForm.controls.password.value,
- verify_password: this.resetPasswordForm.controls.confirmPassword.value
- };
-
- const nrResetPasswordTracer = this.newRelic.createTracer('reset password');
-
- this.authService.resetPassword(data).subscribe(
- res => {
- nrResetPasswordTracer();
- return this._notifyAndRedirect('Password successfully changed! Please login with the new password.');
- },
- err => {
- nrResetPasswordTracer();
- this.newRelic.noticeError('reset password failed', JSON.stringify(err));
- if (this.utils.has(err, 'data.type')) {
- if (err.data.type === 'password_compromised') {
- return this.notificationService.alert({
- message: `We’ve checked this password against a global database of insecure passwords and your password was on it.
- Please try again.
- You can learn more about how we check that database `,
- buttons: [
- {
- text: 'OK',
- role: 'cancel'
- }
- ],
- });
- }
- }
- return this.notificationService.presentToast('Error updating password.Try again');
- }
- );
- }
-
- checkPasswordMatching(resetPasswordForm: FormGroup) {
- const password = resetPasswordForm.controls.password.value;
- const confirmPassword = resetPasswordForm.controls.confirmPassword.value;
-
- return password === confirmPassword ? null : { notMatching : true };
- }
-
- private _notifyAndRedirect(msg) {
- this.notificationService.alert({
- message: msg,
- buttons: [
- {
- text: 'OK',
- role: 'cancel',
- handler: () => {
- this.router.navigate(['login']);
- }
- }
- ]
- });
- }
-
-}
diff --git a/src/app/auth/auth-routing.module.ts b/src/app/auth/auth-routing.module.ts
deleted file mode 100644
index c541cb37c..000000000
--- a/src/app/auth/auth-routing.module.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
-
-import { AuthComponent } from './auth.component';
-import { AuthLoginComponent } from './auth-login/auth-login.component';
-import { AuthLogoutComponent } from './auth-logout/auth-logout.component';
-import { AuthForgotPasswordComponent } from './auth-forgot-password/auth-forgot-password.component';
-import { AuthRegistrationComponent } from './auth-registration/auth-registration.component';
-import { AuthResetPasswordComponent } from './auth-reset-password/auth-reset-password.component';
-import { AuthDirectLoginComponent } from './auth-direct-login/auth-direct-login.component';
-import { AuthGlobalLoginComponent } from './auth-global-login/auth-global-login.component';
-import { UnauthorizedGuard } from './unauthorized.guard';
-
-const routes: Routes = [
- {
- path: '',
- component: AuthComponent,
- children: [
- {
- path: '',
- redirectTo: '/login',
- pathMatch: 'full',
- },
- {
- path: 'login',
- component: AuthLoginComponent,
- canActivate: [UnauthorizedGuard],
- },
- {
- path: 'logout',
- component: AuthLogoutComponent,
- },
- {
- path: 'forgot_password',
- component: AuthForgotPasswordComponent,
- canActivate: [UnauthorizedGuard],
- },
- {
- path: 'registration/:email/:key',
- component: AuthRegistrationComponent,
- canActivate: [UnauthorizedGuard],
- },
- {
- path: 'reset_password/:key/:email',
- component: AuthResetPasswordComponent,
- canActivate: [UnauthorizedGuard],
- },
- {
- path: 'secure/:authToken',
- component: AuthDirectLoginComponent,
- },
- {
- path: 'global_login/:apikey',
- component: AuthGlobalLoginComponent,
- }
- ]
- }
-];
-
-@NgModule({
- imports: [RouterModule.forChild(routes)],
- exports: [RouterModule]
-})
-export class AuthRoutingModule {}
diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts
deleted file mode 100644
index 540a38c80..000000000
--- a/src/app/auth/auth.component.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
- selector: 'app-auth',
- template: ' '
-})
-export class AuthComponent {}
diff --git a/src/app/auth/auth.guard.ts b/src/app/auth/auth.guard.ts
deleted file mode 100644
index 4d98d9b31..000000000
--- a/src/app/auth/auth.guard.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { Injectable } from '@angular/core';
-import {
- CanActivate, Router,
- ActivatedRouteSnapshot,
- RouterStateSnapshot,
- CanActivateChild,
- NavigationExtras,
- CanLoad, Route
-} from '@angular/router';
-import { AuthService } from './auth.service';
-
-@Injectable({
- providedIn: 'root',
-})
-export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
- constructor(private authService: AuthService, private router: Router) {}
-
- canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
- return this.checkLogin();
- }
-
- canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
- return this.canActivate(route, state);
- }
-
- canLoad(route: Route): boolean {
- return this.checkLogin();
- }
-
- checkLogin(): boolean {
- if (this.authService.isAuthenticated()) {
- return true;
- }
-
- this.router.navigate(['/login']);
- return false;
- }
-}
diff --git a/src/app/auth/auth.module.ts b/src/app/auth/auth.module.ts
deleted file mode 100644
index 41bc46701..000000000
--- a/src/app/auth/auth.module.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { RouterModule } from '@angular/router';
-import { NgModule } from '@angular/core';
-import { SharedModule } from '@shared/shared.module';
-import { ReactiveFormsModule } from '@angular/forms';
-
-import { AuthRoutingModule } from './auth-routing.module';
-
-import { AuthService } from './auth.service';
-
-import { UnauthorizedGuard } from './unauthorized.guard';
-import { ProgramSelectedGuard } from './program-selected.guard';
-import { AuthComponent } from './auth.component';
-import { AuthLoginComponent } from './auth-login/auth-login.component';
-import { AuthLogoutComponent } from './auth-logout/auth-logout.component';
-import { AuthForgotPasswordComponent } from './auth-forgot-password/auth-forgot-password.component';
-import { AuthRegistrationComponent } from './auth-registration/auth-registration.component';
-import { AuthResetPasswordComponent } from './auth-reset-password/auth-reset-password.component';
-import { AuthDirectLoginComponent } from './auth-direct-login/auth-direct-login.component';
-import { AuthGlobalLoginComponent } from './auth-global-login/auth-global-login.component';
-import { TermsConditionsPreviewComponent } from './terms-conditions-preview/terms-conditions-preview.component';
-
-@NgModule({
- imports: [
- SharedModule,
- ReactiveFormsModule,
- AuthRoutingModule,
- ],
- declarations: [
- AuthComponent,
- AuthLoginComponent,
- AuthLogoutComponent,
- AuthForgotPasswordComponent,
- AuthRegistrationComponent,
- AuthResetPasswordComponent,
- AuthDirectLoginComponent,
- AuthGlobalLoginComponent,
- TermsConditionsPreviewComponent
- ],
- entryComponents: [
- AuthComponent,
- TermsConditionsPreviewComponent
- ],
- providers: [
- AuthService,
- UnauthorizedGuard,
- ProgramSelectedGuard,
- ],
- exports: [SharedModule]
-})
-export class AuthModule {}
diff --git a/src/app/auth/auth.service.spec.ts b/src/app/auth/auth.service.spec.ts
deleted file mode 100644
index c0c6dd55a..000000000
--- a/src/app/auth/auth.service.spec.ts
+++ /dev/null
@@ -1,280 +0,0 @@
-import { AuthService } from './auth.service';
-import { TestBed } from '@angular/core/testing';
-import { of } from 'rxjs';
-import { RequestService } from '@shared/request/request.service';
-import { TestUtils } from '@testing/utils';
-import { Router } from '@angular/router';
-import { HttpClientModule } from '@angular/common/http';
-import { BrowserStorageService } from '@services/storage.service';
-import { PusherService } from '@shared/pusher/pusher.service';
-import { UtilsService } from '@services/utils.service';
-
-describe('AuthService', () => {
- let service: AuthService;
- let requestSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
- let storageSpy: jasmine.SpyObj;
- let pusherSpy: jasmine.SpyObj;
- let utilsSpy: jasmine.SpyObj;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [HttpClientModule],
- providers: [
- AuthService,
- {
- provide: RequestService,
- useValue: jasmine.createSpyObj('RequestService', ['delete', 'post', 'get', 'put', 'graphQLFetch', 'graphQLWatch'])
- },
- {
- provide: Router,
- useValue: {
- navigate: jasmine.createSpy('navigate'),
- events: of()
- }
- },
- {
- provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', ['setUser', 'getUser', 'set', 'getConfig', 'setConfig', 'get', 'clear'])
- },
- {
- provide: UtilsService,
- useValue: jasmine.createSpyObj('UtilsService', ['has', 'changeThemeColor', 'openUrl'])
- },
- {
- provide: PusherService,
- useValue: jasmine.createSpyObj('PusherService', ['unsubscribeChannels', 'disconnect'])
- },
- ]
- });
- service = TestBed.inject(AuthService);
- requestSpy = TestBed.inject(RequestService) as jasmine.SpyObj;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- storageSpy = TestBed.inject(BrowserStorageService) as jasmine.SpyObj;
- pusherSpy = TestBed.inject(PusherService) as jasmine.SpyObj;
- utilsSpy = TestBed.inject(UtilsService) as jasmine.SpyObj;
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-
-
- it('when testing login(), it should pass the correct data to API', () => {
- requestSpy.post.and.returnValue(of({
- success: true,
- data: {
- tutorial: null,
- apikey: '123456',
- Timelines: [
- {
- Program: {
- config: {
- theme_color: 'abc'
- }
- },
- Enrolment: {},
- Project: {},
- Timeline: {}
- }
- ]
- }
- }));
- storageSpy.getConfig.and.returnValue(true);
- utilsSpy.has.and.returnValue(true);
-
- service.login({ email: 'test@test.com', password: '123' }).subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- expect(requestSpy.post.calls.first().args[0].data).toContain('test%40test.com');
- expect(requestSpy.post.calls.first().args[0].data).toContain('123');
- expect(storageSpy.setUser.calls.first().args[0]).toEqual({ apikey: '123456' });
- });
-
- it('when testing directLogin(), it should pass the correct data to API', () => {
- requestSpy.post.and.returnValue(of({
- success: true,
- data: {
- tutorial: null,
- apikey: '123456',
- Timelines: [
- {
- Program: {
- config: {
- theme_color: 'abc'
- }
- },
- Enrolment: {},
- Project: {},
- Timeline: {}
- }
- ]
- }
- }));
- storageSpy.getConfig.and.returnValue(true);
- service.directLogin({ authToken: 'abcd' }).subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- expect(requestSpy.post.calls.first().args[0].data).toContain('abcd');
- expect(storageSpy.setUser.calls.first().args[0]).toEqual({ apikey: '123456' });
- });
-
- it('when testing globalLogin(), it should pass the correct data to API', () => {
- requestSpy.post.and.returnValue(of({
- success: true,
- data: {
- tutorial: null,
- apikey: '123456',
- Timelines: [
- {
- Program: {
- config: {
- theme_color: 'abc'
- }
- },
- Enrolment: {},
- Project: {},
- Timeline: {}
- }
- ]
- }
- }));
- storageSpy.getConfig.and.returnValue(true);
- service.globalLogin({ apikey: 'abcd', service: 'LOGIN' }).subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- expect(requestSpy.post.calls.first().args[0].data).toContain('abcd');
- expect(storageSpy.setUser.calls.first().args[0]).toEqual({ apikey: '123456' });
- });
-
- describe('when testing isAuthenticated()', () => {
- it('should return true', () => {
- storageSpy.get.and.returnValue(true);
- expect(service.isAuthenticated()).toBe(true);
- });
- it('should return false', () => {
- storageSpy.get.and.returnValue(false);
- expect(service.isAuthenticated()).toBe(false);
- });
- });
-
- describe('when testing logout()', () => {
- it('should navigate to login by default', () => {
- storageSpy.getConfig.and.returnValue({ color: '' });
- service.logout({});
- expect(pusherSpy.unsubscribeChannels.calls.count()).toBe(1);
- expect(pusherSpy.disconnect.calls.count()).toBe(1);
- expect(storageSpy.clear.calls.count()).toBe(1);
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['login']);
- });
- it('should pass navigation data', () => {
- storageSpy.getConfig.and.returnValue({ color: '' });
- service.logout({ data: 'data' });
- expect(pusherSpy.unsubscribeChannels.calls.count()).toBe(1);
- expect(pusherSpy.disconnect.calls.count()).toBe(1);
- expect(storageSpy.clear.calls.count()).toBe(1);
- expect(routerSpy.navigate.calls.first().args[0]).toEqual(['login'], { data: 'data' });
- });
-
- it('should not navigate to login when it is called with redirect = false', () => {
- storageSpy.getConfig.and.returnValue({ color: '' });
- service.logout({}, false);
- expect(routerSpy.navigate.calls.count()).toBe(0);
- });
- });
-
- it('when testing forgotPassword()', () => {
- requestSpy.post.and.returnValue(of(''));
- service.forgotPassword('test@test.com').subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- expect(requestSpy.post.calls.first().args[0].data.email).toEqual('test@test.com');
- });
-
- it('when testing resetPassword()', () => {
- requestSpy.post.and.returnValue(of(''));
- service.resetPassword({ password: 'abc' }).subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- });
-
- it('when testing connectToLinkedIn()', () => {
- storageSpy.getUser.and.returnValue({ apikey: 'abc', timelineId: 1 });
- storageSpy.get.and.returnValue('aaa');
- service.connectToLinkedIn();
- expect(utilsSpy.openUrl.calls.count()).toBe(1);
- });
-
- describe('when testing contactNumberLogin()', () => {
- it('should set correct data to local storage', () => {
- requestSpy.post.and.returnValue(of({
- data: {
- apikey: 'aaa',
- tutorial: false,
- timelines: []
- }
- }));
- service.contactNumberLogin({ contactNumber: '123' }).subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- expect(storageSpy.setUser.calls.first().args[0].apikey).toEqual('aaa');
- expect(storageSpy.set.calls.count()).toBe(2);
- });
- it('should not set data if response format incorrect', () => {
- requestSpy.post.and.returnValue(of({}));
- service.contactNumberLogin({ contactNumber: '123' }).subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- expect(storageSpy.set.calls.count()).toBe(0);
- });
- });
-
- it('when testing checkDomain()', () => {
- requestSpy.get.and.returnValue(of(''));
- service.checkDomain({ domain: 'localhost' }).subscribe();
- expect(requestSpy.get.calls.count()).toBe(1);
- });
-
- it('when testing updateProfile()', () => {
- requestSpy.post.and.returnValue(of(''));
- service.updateProfile({ contactNumber: '124' }).subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- });
-
- it('when testing saveRegistration()', () => {
- requestSpy.post.and.returnValue(of(''));
- service.saveRegistration({ user_id: 1, password: '123', key: 'key' }).subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- });
-
- it('when testing verifyRegistration()', () => {
- requestSpy.post.and.returnValue(of(''));
- service.verifyRegistration({ email: 'test@test.com', key: 'key' }).subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- });
-
- it('when testing verifyResetPassword()', () => {
- requestSpy.post.and.returnValue(of(''));
- service.verifyResetPassword({ email: 'test@test.com', key: 'key' }).subscribe();
- expect(requestSpy.post.calls.count()).toBe(1);
- });
-
- describe('getUUID()', function () {
- it('should get user uuid in string', () => {
- const UUID = 'SAMPLE-UUID';
- requestSpy.graphQLWatch.and.returnValue(of({
- data: {
- user: {
- uuid: UUID
- }
- }
- }));
- service.getUUID().subscribe(result => {
- expect(result).toBe(UUID);
- });
- });
-
- it('should return null when data object is undefined', () => {
- requestSpy.graphQLWatch.and.returnValue(of({
- data: undefined
- }));
- service.getUUID().subscribe(result => {
- expect(result).toBeNull();
- });
- });
- });
-});
-
diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts
deleted file mode 100644
index 618505ec7..000000000
--- a/src/app/auth/auth.service.ts
+++ /dev/null
@@ -1,387 +0,0 @@
-import { Injectable } from '@angular/core';
-import { QueryEncoder, RequestService } from '@shared/request/request.service';
-import { HttpParams } from '@angular/common/http';
-import { map, tap } from 'rxjs/operators';
-import { Observable, of } from 'rxjs';
-import { Router } from '@angular/router';
-import { BrowserStorageService } from '@services/storage.service';
-import { UtilsService } from '@services/utils.service';
-import { PusherService } from '@shared/pusher/pusher.service';
-import { environment } from '@environments/environment';
-
-/**
- * @name api
- * @description list of api endpoint involved in this service
- * @type {Object}
- */
-const API = {
- getConfig: 'api/v2/plan/experience/list',
- login: 'api/auths.json',
- setProfile: 'api/v2/user/enrolment/edit.json',
- verifyRegistration: 'api/verification_codes.json',
- register: 'api/registration_details.json',
- forgotPassword: 'api/auths.json?action=forgot_password',
- verifyResetPassword: 'api/auths.json?action=verify_reset_password',
- resetPassword: 'api/auths.json?action=reset_password'
-};
-
-interface VerifyParams {
- email: string;
- key: string;
-}
-
-interface RegisterData {
- password: string;
- user_id: number;
- key: string;
-}
-
-interface ConfigParams {
- domain?: string;
- id?: number | string;
- apikey?: string;
-}
-
-interface UserProfile {
- contactNumber: string;
-}
-
-interface ExperienceConfig {
- name: string;
- config?: {
- theme_color?: string;
- card_style?: string;
- review_rating?: boolean;
- review_rating_notification?: boolean;
- deep_link_in_app?: boolean;
- achievement_in_app_mentor?: boolean;
- achievement_in_app_participant?: boolean;
- };
- logo: string;
-}
-
-@Injectable({
- providedIn: 'root'
-})
-export class AuthService {
- // added to cache deeplink for appv3 switching (purpose: global var reference)
- deeplink: string;
-
- constructor(
- private request: RequestService,
- private storage: BrowserStorageService,
- private utils: UtilsService,
- private router: Router,
- private pusherService: PusherService
- ) {}
-
- private _login(body: HttpParams, serviceHeader?: string) {
- const headers = {
- 'Content-Type': 'application/x-www-form-urlencoded',
- service: serviceHeader
- };
- if (!serviceHeader) {
- delete headers.service;
- }
- return this.request.post({
- endPoint: API.login,
- data: body.toString(),
- httpOptions: {
- headers
- },
- customErrorHandler: (err: any) => {
- console.log('catchError::', err);
- return of(err);
- }
- }).pipe(tap(res => {
- if (res?.data?.appv3 === true) {
- if (this.deeplink) {
- const onePageOnly = this.deeplink.match(/(one_page_only=true)/g);
- const redirectReview = this.deeplink.match(/(redirect=review)/g);
- if (onePageOnly !== null && redirectReview !== null) { // temporary allow review to be done on AppV2
- this.deeplink = null;
- return;
- }
- }
-
- this.storage.setAppV3(true);
- let finalURL = '';
-
- if (this.deeplink) {
- finalURL = this.deeplink.replace(/https?\:\/\/[\w\W]+\//g, environment.appv3URL);
- } else {
- finalURL = `${environment.appv3URL}?apikey=${res.data.apikey}`;
- }
- this.utils.redirectToUrl(finalURL);
- return;
- }
- }), map(res => this._handleLoginResponse(res)));
- }
-
- /**
- * @name login
- * @description login API specifically only accept request data in encodedUrl formdata,
- * so must convert them into compatible formdata before submission
- * @param {object} { email, password } in string for each of the value
- */
- login({ email, password }): Observable {
- const body = new HttpParams({
- encoder: new QueryEncoder()
- })
- .set('data[User][email]', email)
- .set('data[User][password]', password)
- .set('domain', this.getDomain());
-
- return this._login(body);
- }
-
- /**
- * @name directLogin
- * @description login API specifically only accept request data in encodedUrl formdata,
- * so must convert them into compatible formdata before submission
- * @param {object} { authToken } in string
- */
- directLogin({ authToken }): Observable {
- const body = new HttpParams()
- .set('auth_token', authToken);
- this.logout({}, false);
- return this._login(body);
- }
-
- /**
- * @name globalLogin
- * @description login API specifically only accept request data in encodedUrl formdata,
- * so must convert them into compatible formdata before submission
- * @param {object} { apikey } in string
- */
- globalLogin({ apikey, service }): Observable {
- const body = new HttpParams()
- .set('apikey', apikey);
- this.logout({}, false);
- return this._login(body, service);
- }
-
- private _handleLoginResponse(response): Observable {
- const norm = this._normaliseAuth(response);
- this.storage.setUser({ apikey: norm.apikey });
- this.storage.set('programs', norm.programs);
- this.storage.set('isLoggedIn', true);
- return norm;
- }
-
- private _normaliseAuth(rawData): any {
- const data = rawData.data;
- return {
- success: rawData.success,
- tutorial: data.tutorial,
- apikey: data.apikey,
- programs: data.Timelines.map(
- timeline => {
- // make sure 'Program.config.theme_color' exist
- if (!this.utils.has(timeline, 'Program.config.theme_color')) {
- if (!this.utils.has(timeline.Program, 'config')) {
- timeline.Program.config = {
- theme_color: 'var(--ion-color-primary)'
- };
- } else {
- timeline.Program.config.theme_color = 'var(--ion-color-primary)';
- }
- }
- return {
- enrolment: timeline.Enrolment,
- program: timeline.Program,
- project: timeline.Project,
- timeline: timeline.Timeline,
- experience: timeline.Experience,
- };
- },
- this
- ),
- config: (data.Experience || {}).config || {},
- _raw: rawData
- };
- }
-
- isAuthenticated(): boolean {
- return this.storage.get('isLoggedIn');
- }
-
- /**
- * Clear user's information and log the user out
- * @param navigationParams the parameters needed when redirect
- * @param redirect Whether redirect the user to login page or not
- */
- logout(navigationParams = {}, redirect = true) {
- // use the config color
- this.utils.changeThemeColor(this.storage.getConfig().colors);
- this.pusherService.unsubscribeChannels();
- this.pusherService.disconnect();
- const config = this.storage.getConfig();
-
- this.storage.clear();
- // still store config info even logout
- this.storage.setConfig(config);
-
- if (redirect) {
- return this.router.navigate(['login'], navigationParams);
- }
- }
-
- /**
- * @name forgotPassword
- * @description make request to server to send out email with reset password url
- * @param {string}} email [user's email which will receive reset password url]
- * @return {Observable} [description]
- */
- forgotPassword(email: string): Observable {
- return this.request.post({
- endPoint: API.forgotPassword,
- data: {
- email: email,
- domain: this.getDomain(),
- }
- });
- }
-
- getDomain() {
- let domain = window.location.hostname;
- domain =
- domain.indexOf('127.0.0.1') !== -1 ||
- domain.indexOf('localhost') !== -1
- ? 'dev.app-v2.practera.com'
- : domain;
- return domain;
- }
-
- /**
- * @name resetPassword
- * @description make request to server to reset user password
- * @param {[type]} data [description]
- * @return {Observable} [description]
- */
- resetPassword(data): Observable {
- return this.request.post({
- endPoint: API.resetPassword, data
- });
- }
-
- // Activity ID is no longer used as a parameter,
- // but needs to be there so just pass in a 1
- connectToLinkedIn() {
- const url = '/api/auth_linkedin.json?apikey=' + this.storage.getUser().apikey + '&appkey=' + this.storage.get('appkey') + '&timeline_id=' + this.storage.getUser().timelineId;
-
- this.utils.openUrl(url);
- return;
- }
-
- /**
- * @name contactNumberLogin
- * @description fast/quick login with contact number
- * @param {string}} data [description]
- * @return {Observable} [description]
- */
- contactNumberLogin(data: { contactNumber: string }): Observable {
- return this.request.post(
- {
- endPoint: API.login,
- data: {
- contact_number: data.contactNumber,
- }
- }).pipe(map(response => {
- if (response.data) {
- this.storage.setUser({ apikey: response.data.apikey });
- this.storage.set('tutorial', response.data.tutorial);
- this.storage.set('programs', response.data.timelines);
- }
-
- // @TODO: verify if safari browser localStorage store data above properly
- return response;
- }));
- }
-
- getConfig(data: ConfigParams): Observable<{ data: ExperienceConfig[] }> {
- return this.request.get(API.getConfig, {
- params: data
- });
- }
-
- /**
- * @name checkDomain
- * @description enforced domain checking before experience config API call
- * @param {[type]} data [description]
- */
- checkDomain(data): Observable