diff --git a/src/app/client/src/app/modules/core/services/permission/permission.service.ts b/src/app/client/src/app/modules/core/services/permission/permission.service.ts index e7bfd5492f8cac1931e272443acea88a4d344619..6bd86b0862f227fd6f7622746d7a0cbd12a877eb 100644 --- a/src/app/client/src/app/modules/core/services/permission/permission.service.ts +++ b/src/app/client/src/app/modules/core/services/permission/permission.service.ts @@ -2,10 +2,10 @@ import { ConfigService, ServerResponse, ToasterService, ResourceService, IUserDa import { LearnerService } from './../learner/learner.service'; import { UserService } from '../user/user.service'; import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; import * as _ from 'lodash-es'; import { RolesAndPermissions, Roles } from './../../interfaces'; - +import { Observable, BehaviorSubject } from 'rxjs'; +import { shareReplay, tap } from 'rxjs/operators'; /** * Service to fetch permission and validate user permission * @@ -41,34 +41,16 @@ export class PermissionService { * 3.error if server error while fetching roles. */ public permissionAvailable$ = new BehaviorSubject<string>(undefined); - /** - * reference of ResourceService service. - */ - public resourceService: ResourceService; - /** - * reference of config service. - */ - public config: ConfigService; - /** - * reference of LearnerService service. - */ - public learner: LearnerService; - /** - * reference of UserService service. - */ - public userService: UserService; + + public availableRoles$: Observable<any> = this.getPermissionsData().pipe(shareReplay(1)); /** * constructor * @param {ConfigService} config ConfigService reference * @param {LearnerService} learner LearnerService reference * @param {UserService} userService UserService reference */ - constructor(resourceService: ResourceService, config: ConfigService, - learner: LearnerService, userService: UserService, public toasterService: ToasterService) { - this.config = config; - this.learner = learner; - this.userService = userService; - this.resourceService = resourceService; + constructor(private resourceService: ResourceService, private config: ConfigService, + private learner: LearnerService, private userService: UserService, public toasterService: ToasterService) { } public initialize() { this.setCurrentRoleActions(); @@ -76,11 +58,11 @@ export class PermissionService { /** * method to fetch organization permission and roles. */ - private getPermissionsData(): void { + private getPermissionsData() { const option = { url: this.config.urlConFig.URLS.ROLES.READ }; - this.learner.get(option).subscribe( + return this.learner.get(option).pipe(tap( (data: ServerResponse) => { if (data.result.roles) { this.setRolesAndPermissions(data.result.roles); @@ -89,7 +71,7 @@ export class PermissionService { (err: ServerResponse) => { this.toasterService.error('Something went wrong, please try again later...'); } - ); + )); } /** * method to process roles and actions @@ -107,7 +89,6 @@ export class PermissionService { this.rolesAndPermissions.push(mainRole); }); this.rolesAndPermissions = _.uniqBy(this.rolesAndPermissions, 'role'); - this.setCurrentRoleActions(); } /** * method to process logged in user roles and actions diff --git a/src/app/client/src/app/modules/player-helper/components/credits-and-licence/credits-and-licence.component.html b/src/app/client/src/app/modules/player-helper/components/credits-and-licence/credits-and-licence.component.html index 4ac43ed7142987b8c290e809f1f4874b33c7ea07..a809560ad7d6be8e51b77cebe32195fbfc9aaf6f 100644 --- a/src/app/client/src/app/modules/player-helper/components/credits-and-licence/credits-and-licence.component.html +++ b/src/app/client/src/app/modules/player-helper/components/credits-and-licence/credits-and-licence.component.html @@ -18,19 +18,19 @@ <div content class="sb-accordion-content sb-bg-color-white pt-0 pb-8"> - <div class="py-8"> - <div class="content-metadeta__title fxsmall mb-4" *ngIf="contentData?.author">{{resourceService?.frmelmnts?.lbl?.author}}</div> + <div class="py-8" *ngIf="contentData?.author"> + <div class="content-metadeta__title fxsmall mb-4">{{resourceService?.frmelmnts?.lbl?.author}}</div> <div class="content-metadeta__text fnormal">{{contentData?.author}}</div> </div> - <div class="py-8"> - <div class="content-metadeta__title fxsmall mb-4" *ngIf="attributions">{{resourceService?.frmelmnts?.lbl?.attributions}}</div> + <div class="py-8" *ngIf="attributions"> + <div class="content-metadeta__title fxsmall mb-4">{{resourceService?.frmelmnts?.lbl?.attributions}}</div> <div class="content-metadeta__text fnormal">{{attributions}}</div> </div> - <div class="py-8"> - <div class="content-metadeta__title fxsmall mb-4" *ngIf="contentData?.license">{{resourceService?.frmelmnts?.lbl?.licenseTerms}}</div> + <div class="py-8" *ngIf="contentData?.license"> + <div class="content-metadeta__title fxsmall mb-4">{{resourceService?.frmelmnts?.lbl?.licenseTerms}}</div> <div class="content-metadeta__text fnormal">{{contentData.license}}</div> </div> - <div class="py-8"> + <div class="py-8" *ngIf="contentData?.licenseDetails?.name"> <div class="content-metadeta__title fxsmall mb-4"></div> <div class="content-metadeta__text fnormal" *ngIf="contentData?.licenseDetails?.name">{{contentData.licenseDetails['name']}} {{contentData.licenseDetails['description']}}‎ <br /> <span *ngIf="contentData.licenseDetails['url']"> @@ -38,27 +38,27 @@ </span> </div> </div> - <div class="py-8"> - <div class="content-metadeta__title fxsmall mb-4" *ngIf="contentData?.copyright">{{resourceService?.frmelmnts?.lbl?.copyRight}}</div> + <div class="py-8" *ngIf="contentData?.copyright"> + <div class="content-metadeta__title fxsmall mb-4">{{resourceService?.frmelmnts?.lbl?.copyRight}}</div> <div class="content-metadeta__text fnormal">{{contentData?.copyright}}<span *ngIf="contentData?.copyrightYear">, {{contentData?.copyrightYear}}</span></div> </div> <div *ngIf="contentData.originData"> - <div class="content-metadeta__text fnormal sb-color-primary-400" *ngIf="contentData">{{resourceService?.frmelmnts?.lbl?.contentcopiedtitle}}</div> - <div class="py-8"> - <div class="content-metadeta__title fxsmall mb-4" *ngIf="contentData?.originData?.name">{{resourceService?.frmelmnts?.lbl?.desktop.content}}</div> + <div class="content-metadeta__text fnormal sb-color-primary-400" *ngIf="contentData?.originData">{{resourceService?.frmelmnts?.lbl?.contentcopiedtitle}}</div> + <div class="py-8" *ngIf="contentData?.originData?.name"> + <div class="content-metadeta__title fxsmall mb-4">{{resourceService?.frmelmnts?.lbl?.desktop.content}}</div> <div class="content-metadeta__text fnormal">{{contentData?.originData?.name}}</div> </div> - <div class="py-8"> - <div class="content-metadeta__title fxsmall mb-4" *ngIf="contentData?.originData?.author">{{resourceService?.frmelmnts?.lbl?.desktop.authorOfSourceContent}}</div> + <div class="py-8" *ngIf="contentData?.originData?.author"> + <div class="content-metadeta__title fxsmall mb-4">{{resourceService?.frmelmnts?.lbl?.desktop.authorOfSourceContent}}</div> <div class="content-metadeta__text fnormal">{{contentData?.originData?.author}}</div> </div> - <div class="py-8"> - <div class="content-metadeta__title fxsmall mb-4" *ngIf="contentData?.originData?.license">{{resourceService?.frmelmnts?.lbl?.licenseTerms}}</div> + <div class="py-8" *ngIf="contentData?.originData?.license"> + <div class="content-metadeta__title fxsmall mb-4">{{resourceService?.frmelmnts?.lbl?.licenseTerms}}</div> <div class="content-metadeta__text fnormal">{{contentData?.originData?.license}}</div> </div> - <div class="py-8"> - <div class="content-metadeta__title fxsmall mb-4" *ngIf="contentData?.originData?.organisation" [innerHTML]="resourceService.frmelmnts.lbl?.publishedOnInstanceName | interpolate:'{instance}': instance"></div> + <div class="py-8" *ngIf="contentData?.originData?.organisation"> + <div class="content-metadeta__title fxsmall mb-4" [innerHTML]="resourceService.frmelmnts.lbl?.publishedOnInstanceName | interpolate:'{instance}': instance"></div> <div class="content-metadeta__text fnormal" *ngIf="contentData?.originData?.organisation">{{contentData?.originData?.organisation.join(', ')}}</div> <a *ngIf="contentData?.orgDetails?.email" href="mailto:{{contentData.orgDetails?.email}}?subject={{contentData.name}}">{{contentData.orgDetails?.email}}</a> diff --git a/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.spec.data.ts b/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.spec.data.ts index de0f846577a60ba2eec274ac08ea700d98d456cc..8a6d4dd7f91dbe6fc2a04dd477ba2cba243fb14a 100644 --- a/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.spec.data.ts +++ b/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.spec.data.ts @@ -728,3 +728,19 @@ export const collectionTree = { 'c_Sunbird_Dev_private_batch_count': 2, 'board': 'CBSE' }; + +export const telemetryErrorData = { + 'context': { + 'env': 'get' + }, + 'object': { + 'id': undefined, + 'type': '', + 'ver': '1.0' + }, + 'edata': { + 'err': '', + 'errtype': 'SYSTEM', + 'stacktrace': '{"message":"contentType field not available","type":"edit","pageid":"get","subtype":"paginate"}' + } +}; diff --git a/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.spec.ts b/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.spec.ts index 60d90718f6e7b58749458534fa788fc958e34e4b..eafa353825731267fcf52bf0ea6537e7319b7d2d 100644 --- a/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.spec.ts +++ b/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.spec.ts @@ -8,9 +8,10 @@ import { CollectionHierarchyAPI, ContentService, CoreModule } from '@sunbird/cor import { PublicCollectionPlayerComponent } from './public-collection-player.component'; import { PublicPlayerService } from './../../../../services'; import { ActivatedRoute, Router } from '@angular/router'; -import { TelemetryModule } from '@sunbird/telemetry'; -import { WindowScrollService, SharedModule, ResourceService, ToasterService , NavigationHelperService} from '@sunbird/shared'; -import { CollectionHierarchyGetMockResponse, collectionTree } from './public-collection-player.component.spec.data'; +import { TelemetryModule, TelemetryService } from '@sunbird/telemetry'; +import { WindowScrollService, SharedModule, ResourceService, ToasterService, NavigationHelperService } from '@sunbird/shared'; +import { CollectionHierarchyGetMockResponse, collectionTree, + telemetryErrorData } from './public-collection-player.component.spec.data'; import { DeviceDetectorService } from 'ngx-device-detector'; describe('PublicCollectionPlayerComponent', () => { @@ -18,6 +19,7 @@ describe('PublicCollectionPlayerComponent', () => { let fixture: ComponentFixture<PublicCollectionPlayerComponent>; const collectionId = 'do_112270591840509952140'; const contentId = 'domain_44689'; + let telemetryService; const fakeActivatedRoute = { params: observableOf({ collectionId: collectionId }), // queryParams: Observable.of({ contentId: contentId }), @@ -54,17 +56,21 @@ describe('PublicCollectionPlayerComponent', () => { } } }; + class TelemetryServiceStub { + error = jasmine.createSpy('error').and.returnValue(true); + } beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [PublicCollectionPlayerComponent], imports: [CoreModule, HttpClientTestingModule, RouterTestingModule, TelemetryModule.forRoot(), SharedModule.forRoot()], - providers: [ContentService, PublicPlayerService, ResourceService, - ToasterService, NavigationHelperService, - { provide: Router, useClass: RouterStub }, - { provide: ActivatedRoute, useValue: fakeActivatedRoute }, - { provide: ResourceService, useValue: resourceBundle }], - schemas: [NO_ERRORS_SCHEMA] + providers: [ContentService, PublicPlayerService, ResourceService, + ToasterService, NavigationHelperService, + { provide: Router, useClass: RouterStub }, + { provide: ActivatedRoute, useValue: fakeActivatedRoute }, + { provide: ResourceService, useValue: resourceBundle }, + { provide: TelemetryService, useValue: new TelemetryServiceStub() }], + schemas: [NO_ERRORS_SCHEMA], }) .compileComponents(); })); @@ -72,6 +78,7 @@ describe('PublicCollectionPlayerComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(PublicCollectionPlayerComponent); component = fixture.componentInstance; + telemetryService = TestBed.get(TelemetryService); }); it('should create', () => { const windowScrollService = TestBed.get(WindowScrollService); @@ -189,4 +196,25 @@ describe('PublicCollectionPlayerComponent', () => { component.printPdf('www.samplepdf.com'); expect(window.open).toHaveBeenCalledWith('www.samplepdf.com', '_blank'); }); + it('should call triggerTelemetryErrorEvent', () => { + spyOn(component, 'triggerTelemetryErrorEvent').and.callThrough(); + component.triggerTelemetryErrorEvent(404, 'contentType field not available'); + expect(component.triggerTelemetryErrorEvent).toHaveBeenCalledWith(404, 'contentType field not available'); + expect(telemetryService.error).toHaveBeenCalled(); + expect(telemetryService.error).toHaveBeenCalledTimes(1); + expect(telemetryService.error).toHaveBeenCalledWith(telemetryErrorData); + }); + it('should call getTelemetryErrorData', () => { + const stacktrace = { + message: 'contentType field not available', + type: 'view' + }; + const result = component.getTelemetryErrorData(stacktrace); + expect(result.context.env).toEqual('get'); + expect(result.object.ver).toEqual('1.0'); + expect(result.edata.errtype).toEqual('SYSTEM'); + expect(JSON.parse(result.edata.stacktrace).message).toEqual(stacktrace.message); + expect(JSON.parse(result.edata.stacktrace).type).toEqual('view'); + expect(result).toBeTruthy(); + }); }); diff --git a/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.ts b/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.ts index 540939da66467133d5017bacb1e0944213d43582..2277933b83529e749a775389a19834202075aabc 100644 --- a/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.ts +++ b/src/app/client/src/app/modules/public/module/player/components/public-collection-player/public-collection-player.component.ts @@ -12,7 +12,8 @@ import { } from '@sunbird/shared'; import { CollectionHierarchyAPI, ContentService, UserService } from '@sunbird/core'; import * as _ from 'lodash-es'; -import { IInteractEventObject, IInteractEventEdata, IImpressionEventInput, IEndEventInput, IStartEventInput } from '@sunbird/telemetry'; +import { IInteractEventObject, IInteractEventEdata, IImpressionEventInput, IEndEventInput, IStartEventInput, + TelemetryService } from '@sunbird/telemetry'; import * as TreeModel from 'tree-model'; import { PopupControlService } from '../../../../../../service/popup-control.service'; @Component({ @@ -122,7 +123,8 @@ export class PublicCollectionPlayerComponent implements OnInit, OnDestroy, After public externalUrlPreviewService: ExternalUrlPreviewService, private configService: ConfigService, public toasterService: ToasterService, private contentUtilsService: ContentUtilsServiceService, public popupControlService: PopupControlService, - public utilService: UtilService, public userService: UserService) { + public utilService: UtilService, public userService: UserService, + public telemetryService: TelemetryService) { this.contentService = contentService; this.playerService = playerService; this.windowScrollService = windowScrollService; @@ -201,11 +203,15 @@ export class PublicCollectionPlayerComponent implements OnInit, OnDestroy, After ngAfterViewInit () { this.pageLoadDuration = this.navigationHelperService.getPageLoadTime(); + let cData = []; + if (this.contentType) { + cData = [{id: this.activatedRoute.snapshot.params.collectionId, type: this.contentType}]; + } setTimeout(() => { this.telemetryImpression = { context: { env: this.route.snapshot.data.telemetry.env, - cdata: [{id: this.activatedRoute.snapshot.params.collectionId, type: this.contentType}] + cdata: cData }, object: { id: this.activatedRoute.snapshot.params.collectionId, @@ -221,6 +227,9 @@ export class PublicCollectionPlayerComponent implements OnInit, OnDestroy, After } }; }); + if (!this.contentType) { + this.triggerTelemetryErrorEvent(404, 'contentType field unavailable'); + } } ngOnDestroy() { @@ -251,7 +260,7 @@ export class PublicCollectionPlayerComponent implements OnInit, OnDestroy, After this.isContentPresent = false; } } - setTelemetryContentImpression (data) { + setTelemetryContentImpression(data) { this.telemetryContentImpression = { context: { env: this.route.snapshot.data.telemetry.env @@ -431,11 +440,15 @@ export class PublicCollectionPlayerComponent implements OnInit, OnDestroy, After private setTelemetryStartEndData() { const deviceInfo = this.deviceDetectorService.getDeviceInfo(); + let cData = []; + if (this.contentType) { + cData = this.telemetryCdata; + } setTimeout(() => { this.telemetryCourseStart = { context: { env: this.route.snapshot.data.telemetry.env, - cdata: this.telemetryCdata + cdata: cData }, object: { id: this.collectionId, @@ -464,7 +477,7 @@ export class PublicCollectionPlayerComponent implements OnInit, OnDestroy, After }, context: { env: this.route.snapshot.data.telemetry.env, - cdata: this.telemetryCdata + cdata: cData }, edata: { type: this.route.snapshot.data.telemetry.type, @@ -472,6 +485,9 @@ export class PublicCollectionPlayerComponent implements OnInit, OnDestroy, After mode: 'play' } }; + if (!this.contentType) { + this.triggerTelemetryErrorEvent(404, 'contentType field unavailable'); + } } callinitPlayer (event) { if ((event.data.identifier !== _.get(this.activeContent, 'identifier')) || this.isMobile ) { @@ -494,6 +510,35 @@ export class PublicCollectionPlayerComponent implements OnInit, OnDestroy, After this.callinitPlayer(event); } + triggerTelemetryErrorEvent (status, message) { + const stacktrace = { + message: message, + type: this.route.snapshot.data.telemetry.type, + pageid: this.route.snapshot.data.telemetry.pageid, + collectionId: this.collectionId, + subtype: this.route.snapshot.data.telemetry.subtype, + url: this.userService.slug ? '/' + this.userService.slug + this.router.url : this.router.url + }; + const telemetryErrorData = this.getTelemetryErrorData(stacktrace); + this.telemetryService.error(telemetryErrorData); + } + + getTelemetryErrorData(stacktrace) { + return { + context: { env: this.route.snapshot.data.telemetry.env }, + object: { + id: this.collectionId, + type: this.contentType || '', + ver: '1.0', + }, + edata: { + err: status.toString(), + errtype: 'SYSTEM', + stacktrace: JSON.stringify(stacktrace) + } + }; + } + getContentRollUp(rollup: string[]) { const objectRollUp = {}; if (rollup) { diff --git a/src/app/client/src/app/modules/search/components/user-edit/user-edit.component.ts b/src/app/client/src/app/modules/search/components/user-edit/user-edit.component.ts index ac60f5c2b421b8916d0e2dd2e1bcc96e51f8c802..e777a27c9327530bbb7cc7c6223170a9e687b386 100644 --- a/src/app/client/src/app/modules/search/components/user-edit/user-edit.component.ts +++ b/src/app/client/src/app/modules/search/components/user-edit/user-edit.component.ts @@ -81,10 +81,8 @@ export class UserEditComponent implements OnInit, OnDestroy, AfterViewInit { this.userId = params.userId; }); this.populateUserDetails(); - this.permissionService.permissionAvailable$.subscribe(params => { - if (params === 'success') { - this.allRoles = this.permissionService.allRoles; - } + this.permissionService.availableRoles$.subscribe(params => { + this.allRoles = this.permissionService.allRoles; this.allRoles = _.filter(this.allRoles, (role) => { return role.role !== 'ORG_ADMIN' && role.role !== 'SYSTEM_ADMINISTRATION' && role.role !== 'ADMIN'; }); diff --git a/src/app/client/src/app/modules/search/components/user-filter/user-filter.component.ts b/src/app/client/src/app/modules/search/components/user-filter/user-filter.component.ts index 6f313e06d5afcfb645124871210bc384109da827..3f8337c22886fdb91b5662f26beceab5b15725e8 100644 --- a/src/app/client/src/app/modules/search/components/user-filter/user-filter.component.ts +++ b/src/app/client/src/app/modules/search/components/user-filter/user-filter.component.ts @@ -163,10 +163,8 @@ export class UserFilterComponent implements OnInit { } getRoles() { - return this.permissionService.permissionAvailable$.pipe(map((res) => { - if (res === 'success') { - this.allRoles = this.permissionService.allRoles; - } + return this.permissionService.availableRoles$.pipe(map((res) => { + this.allRoles = this.permissionService.allRoles; this.allRoles = _.filter(this.allRoles, (role) => { return role.role !== 'ORG_ADMIN' && role.role !== 'SYSTEM_ADMINISTRATION' && role.role !== 'ADMIN'; }); diff --git a/src/app/client/src/app/modules/search/components/user-search/user-search.component.ts b/src/app/client/src/app/modules/search/components/user-search/user-search.component.ts index 84d3d6f919f40d373c430f101d93113111a63182..9767e0a1844c721b43e169c05b29e76b99863f8a 100644 --- a/src/app/client/src/app/modules/search/components/user-search/user-search.component.ts +++ b/src/app/client/src/app/modules/search/components/user-search/user-search.component.ts @@ -299,13 +299,11 @@ export class UserSearchComponent implements OnInit, AfterViewInit { this.queryParams = { ...bothParams.queryParams }; this.selectedRoles = []; if (this.queryParams.Roles) { - this.permissionService.permissionAvailable$.subscribe(params => { - if (params === 'success') { - _.forEach(this.permissionService.allRoles, (role) => { - if (this.queryParams.Roles.includes(role.roleName)) { this.selectedRoles.push(role.role); } - }); - this.populateUserSearch(); - } + this.permissionService.availableRoles$.subscribe(params => { + _.forEach(this.permissionService.allRoles, (role) => { + if (this.queryParams.Roles.includes(role.roleName)) { this.selectedRoles.push(role.role); } + }); + this.populateUserSearch(); }); } else { this.populateUserSearch();