Angular Component ViewChild object is null - rxjs

I have below code written in Angular 10 with Rx.js and Nxdev.
I am getting object eventInfoCardStep2 & eventInfoCardStep3 as null due to which further code of downloading excel does not work.
If i comment below error giving lines then file get download but modal spinner does not stop.
this.eventInfoCardStep2.displaySpinner = false;
this.eventInfoCardStep3.displaySpinner = false;
export class EventInfoCardComponent
extends AbstractV2Component
implements OnChanges {
#Input() assetEventDetails: AssetEvent;
showEventInfoCard = true;
uploadedFileNameList: string[] = [];
uploadedFileList: TableDataRow[] = [];
eventBulkTransactionId = '';
displaySpinner = false;
constructor(
private store: Store<fromRoot.State>,
private stringProcessService: StringProcessService
) {
super();
}
}
import { Actions, ofType } from '#ngrx/effects';
import { filter, takeUntil } from 'rxjs/operators';
import { Subscription } from 'rxjs';
export class EventStepperComponent
extends AbstractV2Component
implements OnInit, OnDestroy {
#ViewChild('eventInfoCardStep2')
eventInfoCardStep2: EventInfoCardComponent;
#ViewChild('eventInfoCardStep3', { static: false })
eventInfoCardStep3: EventInfoCardComponent;
this.eventCardSubscriptions = [];
const subs1: Subscription = this.actions$
.pipe(
filter(
(data) =>
data.type ===
assetEventActions.AssetEventActionType
.DOWNLOAD_ASSET_EVENT_ATTACHMENT_FROMCARD_SUCCESS
),
takeUntil(this.unsubscribe$)
)
.subscribe(
(
action: assetEventActions.DownloadAssetEventAttachmentFromCardSuccess
) => {
this.displaySpinner = false;
this.eventInfoCardStep2.displaySpinner = false;
this.eventInfoCardStep3.displaySpinner = false;
if (action.response.size !== 0) {
const blob = new Blob([action.response], {
type: 'application/octatestream',
});
fileSaver.saveAs(blob, action.fileName.trim());
}
}
);
}

Related

Angular unit testing for the promises

I am trying to write the test cases for below method :-
The below is the component code for adding the product
COMPONENT:
import { HttpClient } from '#angular/common/http';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { Observable } from 'rxjs';
import { Constants } from '../../../../../../utils/constants';
import { FileUploadService } from '../../../services/file-upload.service';
import { Component, EventEmitter, Input, OnInit, Output } from '#angular/core';
import { ManagePackageService } from '../../../services/manage-package.service';
import { DomSanitizer } from '#angular/platform-browser';
import { Package } from '../../../models/package.model';
import { ProductPackageMapping } from '../../../models/product-package-mapping.model';
import { Product } from '../../../models/product.model';
import { NotificationAlertService } from '../../../../shared/services/notification-alert.service';
import { ManageProductService } from '../../../services/manage-product.service';
import { DocumentDTO } from '../../../models/document-dto';
import { saveAs } from 'file-saver';
import { ConfirmationDialogService } from '../../../../shared/services/confirmation-dialog.service';
#Component({
selector: 'app-add-product',
templateUrl: './add-product.component.html',
styleUrls: ['./add-product.component.scss']
})
export class AddProductComponent implements OnInit {
productDetailForm: FormGroup;
addTitle: boolean = true;
isView: boolean = true;
submitted: boolean = false;
status: boolean = true;
check: boolean = false;
changevar: string;
#Input() id: Number;
#Output() changeIndicator = new EventEmitter<string>();
#Input() list: ProductPackageMapping;
#Output() dataLoaded = new EventEmitter<string>();
fileToUpload: File | null = null;
fileArray: Array<any> = [];
productData: any = [];
productDataTemp: any[];
fData = new FormData();
packagePlan: Array<any> = [];
productDto = new Product();
packageDto = new Package();
productPackageMappingDto = new ProductPackageMapping();
documentDto: Array<any> = [];
isDocId: boolean = false;
productForm: FormGroup;
activeStatus: boolean = true;
requiredFileType: string;
public NAME_MAX_LENGTH = 50;
isValidFormSubmitted: boolean;
#Input() allRolesFromParent: Product[] = [];
selectedFiles?: FileList;
message: string[] = [];
fileInfos?: Observable<any>;
documentList: Array<any> = [];
ppm: any;
fileUrl: any;
constructor(private http: HttpClient,
private formBuilder: FormBuilder,
private uploadService: FileUploadService,
private manageProduct: ManageProductService,
private managePackageService: ManagePackageService,
private sanitizer: DomSanitizer,
private notifyService: NotificationAlertService,
private confirmationDialogService: ConfirmationDialogService
) { }
ngOnInit(): void {
this.createForm();
this.fileInfos = this.uploadService.getFiles();
this.fData = new FormData();
this.subscriptionPackages()
}
closeModal() {
this.status = true;
this.check = false;
this.changevar = 'cancel';
this.changeIndicator.emit(this.changevar);
this.productForm.reset();
this.fData.delete('ppm');
this.fData.delete('file');
this.emit();
}
closeEditModel() {
this.status = true;
this.documentList = [];
this.changevar = "cancel";
this.changeIndicator.emit(this.changevar);
this.addTitle = true;
this.productForm.reset();
this.fData.delete("ppm");
this.fData.delete("file");
this.emit();
}
onChangeSwitch(event) {
if (event.target.checked == true) {
this.activeStatus = true;
} else {
this.activeStatus = false;
}
}
createForm() {
this.productForm = new FormGroup({
name: new FormControl(this.productDto.name, [
Validators.required,
Validators.maxLength(this.NAME_MAX_LENGTH),
Validators.pattern(Constants.NO_WHITE_SPACE_PATTERN),
]),
url: new FormControl(this.productDto.demoURL, Validators.required),
version: new FormControl(this.productDto.productVersion, Validators.required),
projectType: new FormControl(this.productDto.projectType, Validators.required),
plansName: new FormControl(this.packageDto.id, Validators.required),
desc: new FormControl(this.productDto.productDescription, Validators.required),
active: new FormControl(this.productDto.active, Validators.required),
documents: this.formBuilder.array([]),
});
}
ngOnChanges() {
this.productData = this.list;
if (this.id) {
this.addTitle = false;
this.productDataTemp = this.productData.filter((vl) => (this.id == vl.id));
this.productDataTemp.forEach(obj => {
this.productDto.ppmId = obj.ppmId;
this.productDto.id = obj.id;
this.productDto.name = obj.name;
this.productDto.demoURL = obj.demoURL;
this.productDto.productDescription = obj.productDescription;
this.productDto.projectType = obj.type;
this.productDto.active = obj.active;
this.productDto.productVersion = obj.version;
this.packageDto.id = obj.pkgInfo.id;
this.documentList = obj.listDocuments
})
this.createForm();
} else {
this.productDto = new Product();
this.packageDto = new Package();
this.documentDto = [];
this.documentList = [];
}
this.documentList.length == 0 ? this.isDocId = false : this.isDocId = true;
}
get f() {
return this.productForm.controls;
}
choosePackages(e) {
}
subscriptionPackages() {
this.managePackageService.getPackages().subscribe(
(response) => {
this.packagePlan = response;
},
(httpErrorRes) => {
}
);
}
get documentsFormArray(): FormArray {
return this.f['documents'] as FormArray;
}
addDocument() {
if (this.documentsFormArray.length < 5) {
this.documentsFormArray.push(this.formBuilder.group({
id: new FormControl(''),
docName: new FormControl('', Validators.required),
docType: new FormControl('', Validators.required),
attachment: new FormControl('', Validators.required)
}))
}
}
deleteRow(i: number) {
if (this.id) {
delete this.fileArray[i];
this.documentsFormArray.removeAt(i);
this.fileArray.splice(i, 1);
}
else {
delete this.documentsFormArray[i];
this.documentsFormArray.removeAt(i);
this.fileArray.splice(i, 1);
}
}
getFiles(fileArray) {
this.fileArray.forEach(files => {
this.fData.append("file", files);
})
}
selectFiles(event): void {
this.selectedFiles = event.target.files;
}
onFormSubmit() {
this.submitted = true;
if (this.fileArray.length > 0) {
this.getFiles(this.fileArray);
}
if (this.productForm.value.id === null) {
this.productDto.id = null;
}
this.productDto.name = this.productForm.value.name;
this.productDto.demoURL = this.productForm.value.url;
this.productDto.productVersion = this.productForm.value.version;
this.productDto.projectType = this.productForm.value.projectType;
this.productDto.productDescription = this.productForm.value.desc;
this.productDto.active = this.productForm.value.active;
this.packageDto.id = this.productForm.value.plansName;
if (this.documentsFormArray && this.documentsFormArray.value && this.documentsFormArray.value.length > 0) {
this.documentsFormArray.value.forEach(element => {
this.documentDto.push(element);
});
}
this.productPackageMappingDto.id = this.productDto.ppmId ? this.productDto.ppmId : null;
this.productPackageMappingDto.products = this.productDto;
this.productPackageMappingDto.packages = this.packageDto;
this.productPackageMappingDto.document = this.documentDto;
this.ppm = new Blob([JSON.stringify(this.productPackageMappingDto)], { type: "application/json" });
this.fData.has("file") ? true : this.fData.append("file", '');
this.fData.append("ppm", this.ppm);
this.confirmationDialogService.confirm('Are you sure ?', 'Do you really want to Activate/ Deactivate Package ?')
.then((confirmed) => {
if (confirmed) {
this.manageProduct.createProduct(this.fData).subscribe(
(response) => {
this.notifyService.showSuccess("Data saved successfully !!", "Success")
this.closeModal();
this.fData.delete("ppm");
this.fData.delete("file");
this.productDto = new Product();
this.packageDto = new Package();
this.documentDto = [];
this.documentList = [];
this.fileArray = [];
this.emit();
},
(httpErrorRes) => {
this.fData.delete("ppm");
this.fData.delete("file");
this.notifyService.showError(httpErrorRes.error, "Error");
}
);
}
else {
this.fData.delete("ppm");
this.fData.delete("file");
}
})
}
upload(event: any) {
let files = event.target.files[0];
this.fileArray.push(files);
}
downloadFile(fileData: DocumentDTO): void {
this.manageProduct.download(fileData.fileName)
.subscribe(blob => saveAs(blob, fileData.fileName));
}
emit() {
this.dataLoaded.emit('AddProductComponent')
}
}
The below is the spec file for the above component
COMPONENT.SPEC.TS
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { NgSelectModule } from '#ng-select/ng-select';
import { TranslateModule } from '#ngx-translate/core';
import { ToastrModule } from 'ngx-toastr';
import { Observable, of, throwError } from 'rxjs';
import { AppConfig } from '../../../../../app.config';
import { ConfirmationDialogService } from '../../../../configurations/services/confirmation-dialog.service';
import { NotificationAlertService } from '../../../../shared/services/notification-alert.service';
import { DocumentDTO } from '../../../models/document-dto';
import { ManagePackageService } from '../../../services/manage-package.service';
import { ManageProductService } from '../../../services/manage-product.service';
import { AddProductComponent } from './add-product.component';
describe('AddProductComponent', () => {
let component: AddProductComponent;
let fixture: ComponentFixture<AddProductComponent>;
let packageServiceStub = jasmine.createSpyObj('ManagePackageService', ['getPackages']);
let confirmationDialougeStub = jasmine.createSpyObj('ConfirmationDialogService', ['confirm']);
let manageProductStub = jasmine.createSpyObj('ManageProductService', ['createProduct','download']);
let notificationStub = jasmine.createSpyObj('NotificationAlertService', ['showSuccess', 'showError']);
let fileData = new DocumentDTO()
class MockUserService {
getPackages() {
return [{'name':'basic', 'id':1}, {'name':'advance', 'id':2}, {'name':'premium', 'id':3}];
}
}
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HttpClientTestingModule,ReactiveFormsModule,FormsModule,ToastrModule.forRoot(),
TranslateModule.forRoot(), NgSelectModule],
declarations: [ AddProductComponent ],
providers :[AppConfig,
{ provide: ManageProductService, useValue: manageProductStub },
{ provide: ManagePackageService, useValue: packageServiceStub }]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddProductComponent);
component = fixture.componentInstance;
const mockpackageResponse = [];
packageServiceStub.getPackages.and.returnValue(of(mockpackageResponse));
component.activeStatus = true;
component.productDataTemp = [{'ppmId': 2, 'id': 2, 'name': 'ttvrtv', 'productDescription': 'vttvt', 'projectType': null}]
component.productData = [{'ppmId': 1, 'id': 1, 'name': 'tvrtv', 'productDescription': 'vtvt', 'projectType': null},
{'ppmId': 2, 'id': 2, 'name': 'ttvrtv', 'productDescription': 'vttvt', 'projectType': null}]
component.id = 2;
component.fileArray = ['dfdfdf','dfdsdf']
component.packagePlan = [{'name':'basic', 'id':1}, {'name':'advance', 'id':2}, {'name':'premium', 'id':3}];
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create closeModal()', () => {
const eventTrue = {
target: {
checked: true,
}
};
const eventFalse = {
target: {
checked: false,
}
};
component.closeModal();
component.closeEditModel();
component.onChangeSwitch(eventTrue)
component.onChangeSwitch(eventFalse)
});
// it('should create ngOnChanges', () => {
// component.ngOnChanges();
// });
it('should create choosePackages', () => {
const event = {};
component.choosePackages(event);
});
it('should create subscriptionPackages()', () => {
component.subscriptionPackages();
expect(component.packagePlan).toBeTruthy();
});
// it('should handle error subscriptionPackages', () => {
// spyOn(window, 'alert');
// packageServiceStub.getPackages.and.returnValue(throwError({ error: 'some error'}));
// component.subscriptionPackages();
// });
it('should create addDocument()', () => {
component.addDocument();
});
it('should create deleteRow(i: number)', () => {
component.id = 1;
component.deleteRow(1);
});
it('should create deleteRow(i: number) nullity check', () => {
component.id = null;
component.deleteRow(null);
});
it('should create selectFiles', () => {
const eventSelectFiles = {
target: {
files: 'ss',
}
};
component.selectFiles(eventSelectFiles);
});
it('should create onFormSubmit length>0', () => {
component.fileArray.length = 2;
component.onFormSubmit();
});
it('should create onFormSubmit nullity check', () => {
component.productForm.value.id = null
component.onFormSubmit();
});
// it('should create onFormSubmit else part', (done) => {
// const confirmationResponse = {}; // Keep is as your response
// let spy = spyOn( confirmationDialougeStub,'confirm').and.returnValue(Promise.resolve(true));
// // fixture.whenStable().then(confirmed => {
// // fixture.detectChanges();
// // component.onFormSubmit();
// // })
// spy.calls.mostRecent().returnValue.then(() => {
// fixture.detectChanges();
// //component.onFormSubmit();
// done();
// });
// });
// it('should create onFormSubmit else part', () => {
// confirmationDialougeStub.confirm.and.returnValue({
// closePromise : {
// then : function(callback) {
// callback({value: true});
// }
// }
// });
// });
it('should create upload download', () => {
const eventFile = {
target: {
files: true,
}
};
component.upload(eventFile);
});
});
PROBLEM :- I have some below pointers regarding Jasmine and trying to figure it out the way to achieve the same.
I am not able to figured out the way to cover or test what the promises return
this.confirmationDialogService.confirm('Are you sure ?', 'Do you really want to Activate/ Deactivate Package ?')
.then((confirmed) => {
In the code coverage, the above lines are not covered and I don't know what went wrong, I have tried so many ways and referred many of the posts from stackoverflow itself but that weren't work for my use case.
how to write test cases for a method having foreach(), filter(), map()
how to write test cases for a method having observables, subscribe and promises
Please suggest the best practices.
Your component is quite long, I will show you how to you can cover the following lines with Promises and Observables.
this.confirmationDialogService.confirm('Are you sure ?', 'Do you really want to Activate/ Deactivate Package ?')
.then((confirmed) => {
if (confirmed) {
this.manageProduct.createProduct(this.fData).subscribe(
Make the following changes:
// dialog is spelt wrong here
let confirmationDialogStub = jasmine.createSpyObj('ConfirmationDialogService', ['confirm']);
....
providers: [
...
// provide the mock for confiramtion dialog service
{ provide: ConfirmationDialogService, usevalue: confirmationDialogStub },
{ provide: ManageProductService, useValue: manageProductStub },
{ provide: ManagePackageService, useValue: packageServiceStub }
],
// fakeAsync because we need tick (wait for promises to resolve before asserting)
it('does abc', fakeAsync(() => {
// mock the value for confirmation with a promise
confirmationDialogStub.confirm.and.returnValue(Promise.resovle(true));
manageProductStub.createProduct.and.returnValue(of({/* mock however you wish */}));
// call the method
component.onFormSubmit();
// wait for all promises to resolve with tick
tick();
// do your assertions
}));
For forEach, filter, map, make sure you mock an array. That the array is there.
That being said, I highly recommend you check out the following resources, they will help you a lot.
https://testing-angular.com/
^ A well written e-book.
https://www.pluralsight.com/courses/unit-testing-angular
^ A really good class on unit testing in Angular.

CRA Boilerplate Cannot read property 'initialLoad' of undefined In Redux -Saga & Reselect & redux-toolkit

Getting This Error When i use a useSelector hook to fetch data.
Im getting initial state undefined which cause the error.
selector.ts
import { createSelector } from '#reduxjs/toolkit';
import { RootState } from 'types';
import { initialState } from '.';
const selectSlice = (state: RootState) => state.initialLoad || initialState;
export const selectInitialLoad = createSelector([selectSlice], state => state);
index.ts
import { PayloadAction } from '#reduxjs/toolkit';
import { createSlice } from 'utils/#reduxjs/toolkit';
import { useInjectReducer, useInjectSaga } from 'utils/redux-injectors';
import { initialLoadSaga } from './saga';
import { InitialLoadState, RepoErrorType } from './types';
export const initialState: InitialLoadState = {
name: 'initial-load',
layouts: [],
loading: false,
error: null,
};
const slice = createSlice({
name: 'initialLoad',
initialState,
reducers: {
loadLayout(state) {
state.loading = true;
state.error = null;
state.layouts = [];
},
layoutLoaded(state, action: PayloadAction<[]>) {
const repos = action.payload;
state.layouts = repos;
state.loading = false;
},
layoutError(state, action: PayloadAction<string | RepoErrorType>) {
state.error = 'error';
state.loading = false;
},
},
});
export const { actions: appLayoutActions } = slice;
export const useAppLayoutSlice = () => {
useInjectReducer({ key: slice.name, reducer: slice.reducer });
useInjectSaga({ key: slice.name, saga: initialLoadSaga });
return { actions: slice.actions };
};
types.ts
/* --- STATE --- */
export interface InitialLoadState {
name: string;
loading: boolean;
error?: RepoErrorType | string | null;
layouts: [];
}
export enum RepoErrorType {
RESPONSE_ERROR = 1,
USER_NOT_FOUND = 2,
USERNAME_EMPTY = 3,
USER_HAS_NO_REPO = 4,
GITHUB_RATE_LIMIT = 5,
}
/*
If you want to use 'ContainerState' keyword everywhere in your feature folder,
instead of the 'InitialLoadState' keyword.
*/
export type ContainerState = InitialLoadState;
reducers.ts
/**
* Combine all reducers in this file and export the combined reducers.
*/
import { combineReducers } from '#reduxjs/toolkit';
import { InjectedReducersType } from 'utils/types/injector-typings';
/**
* Merges the main reducer with the router state and dynamically injected reducers
*/
export function createReducer(injectedReducers: InjectedReducersType = {}) {
// Initially we don't have any injectedReducers, so returning identity function to avoid the error
if (Object.keys(injectedReducers).length === 0) {
return state => state;
} else {
return combineReducers({
...injectedReducers,
});
}
}
this is my configuerStore.ts
/**
* Create the store with dynamic reducers
*/
import {
configureStore,
getDefaultMiddleware,
StoreEnhancer,
} from '#reduxjs/toolkit';
import { createInjectorsEnhancer } from 'redux-injectors';
import createSagaMiddleware from 'redux-saga';
import { createReducer } from './reducers';
export function configureAppStore() {
const reduxSagaMonitorOptions = {};
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
const { run: runSaga } = sagaMiddleware;
// Create the store with saga middleware
const middlewares = [sagaMiddleware];
const enhancers = [
createInjectorsEnhancer({
createReducer,
runSaga,
}),
] as StoreEnhancer[];
const store = configureStore({
reducer: createReducer(),
middleware: [...getDefaultMiddleware(), ...middlewares],
devTools: process.env.NODE_ENV !== 'production',
enhancers,
});
return store;
}
work around
adding a condition to state
import { createSelector } from '#reduxjs/toolkit';
import { RootState } from 'types';
import { initialState } from '.';
const selectSlice = (state: RootState) => state && state.initialLoad || initialState;
export const selectInitialLoad = createSelector([selectSlice], state => state);
most of the files are of create react app boilerplate not change.
i tried the same on the cra boilerplate template without clean setup and it worked fine but after clean and setup it doesnt work how i need it too
Please define initialLoad into /src/types/RootState.ts
Example here

How to change the router history in a functional component using react-redux

I'm trying to migrate a code base from a class component to a functional component, but when I do that my history (browserHistory) and the redirects stop working.
I don't know if it's in the way I'm using useEffect, or forcing the history.push ? But I get a Warning: You cannot change <Router history> on Router and Connected Router. I get the right values from the state and redux and I can see the changes on the location when I console.log it is just that history.push that doesn't happen.
I'm using react-redux and connected-react-router.
What might the problem be?
Use case: Facebook API loads, when user signs in with Facebook, the user is redirected to another page.
This code works: class Component (old code)
class App extends Component {
constructor(props) {
super(props);
this.facebookInterval = undefined;
this.history = createBrowserHistory();
this.state = {
hasFB: false,
error: false,
};
}
/**
* Load the Facebook SDK
*
* #return {void}
*/
componentDidMount() {
this.facebookInterval = setInterval(() => {
if (window.FB !== undefined) {
this.setState({
hasFB: true,
});
clearInterval(this.facebookInterval);
}
}, 100);
window.fbAsyncInit = function () {
window.FB.init({
appId: process.env.REACT_APP_NEW_MESSENGER_APP_ID,
autoLogAppEvents: true,
xfbml: true,
version: 'v3.3',
});
};
(function (d, s, id) {
var js,
fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement(s);
js.id = id;
js.src =
'https://connect.facebook.net/en_US/sdk.js?xfbml=1&version=v9.0&appId=' +
process.env.REACT_APP_NEW_MESSENGER_APP_ID;
fjs.parentNode.insertBefore(js, fjs);
})(document, 'script', 'facebook-jssdk');
}
// [NOTE]: This works when using the class component
// the redirect happens
componentWillUpdate = (nextProps, nextState) => {
console.log('nextProps.redirect', nextProps.redirect)
console.log('nextProps', nextProps)
console.log('nextState', nextState)
if (nextProps.redirect !== undefined && nextProps.redirect !== '/') {
this.history.push(nextProps.redirect);
}
};
render() {
const { t } = this.props;
if (this.state.hasFB) {
return (
<>
<Elements stripe={stripePromise}>
<ConnectedRouter history={this.history}>{routes}</ConnectedRouter>
</Elements>
</>
);
} else {
return null;
}
}
}
const mapStateToProps = (state) => {
let isLoading = state.siteR.isLoading ? 'open' : 'close';
let data = {
redirect: state.siteR.location,
isLoading: isLoading,
user: state.userR[state.userR.id],
id: state.userR.id,
};
if (state.userR[state.userR.id] !== undefined) {
data.models = state.userR[state.userR.id].models;
}
return data;
};
const mapDispatchToProps = (dispatch) => {
return {
// ommited_code
};
};
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withTranslation(),
)(App);
Same code, but on a functional component (new code)
const App = (props) => {
console.log('props', props)
// console.log('props.redirect', props.redirect)
const [hasFB, setHasFB] = useState(false)
const [error, setError] = useState(false)
const [redirect, setRedirect] = useState(props.redirect)
const history = createBrowserHistory()
let facebookInterval = undefined
const {t} = useTranslation()
const initializeFacebook = () => {
facebookInterval = setInterval(() => {
if (window.FB !== undefined) {
setHasFB(true)
clearInterval(facebookInterval);
}
}, 100);
window.fbAsyncInit = function () {
window.FB.init({
appId: process.env.REACT_APP_NEW_MESSENGER_APP_ID,
autoLogAppEvents: true,
xfbml: true,
version: 'v3.3',
});
};
(function (d, s, id) {
var js,
fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement(s);
js.id = id;
js.src =
'https://connect.facebook.net/en_US/sdk.js?xfbml=1&version=v9.0&appId=' +
process.env.REACT_APP_NEW_MESSENGER_APP_ID;
fjs.parentNode.insertBefore(js, fjs);
})(document, 'script', 'facebook-jssdk');
}
// [NOTE]: I get the right values from props.redirect
// and the state
// the redirect just doesnt happen
const handleRedirect = () => {
if (props.redirect !== undefined && props.redirect !== '/') {
console.log('redirect on setRedirect', redirect)
setRedirect(history.push(props.redirect))
}
}
useEffect(() => {
initializeFacebook()
handleRedirect()
console.log('redirect on setRedirect', redirect)
}, [])
if (hasFB) {
return (
<>
<Elements stripe={stripePromise}>
<ConnectedRouter history={history}>{routes}</ConnectedRouter>
</Elements>
</>
);
} else {
return null;
}
}
const mapStateToProps = (state) => {
let isLoading = state.siteR.isLoading ? 'open' : 'close';
let showModelSelect = state.modelR.showModelSelect ? 'open' : 'close';
let showModelAdd = state.modelR.showModelAdd ? 'open' : 'close';
let data = {
redirect: state.siteR.location,
isLoading: isLoading,
user: state.userR[state.userR.id],
id: state.userR.id,
};
if (state.userR[state.userR.id] !== undefined) {
data.models = state.userR[state.userR.id].models;
}
return data;
};
const mapDispatchToProps = (dispatch) => {
return {
// ommited_code
};
};
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withTranslation(),
)(App);
The routes I'm trying to switch to/from
const routes = (
<div>
<Switch>
<SentryRoute exact path='/' component={UserLogin} />
<SentryRoute exact path='/user' component={Profile} />
{/* // omited_code */}
</Switch>
</div>
)
export default routes;
From the documentation, it looks like you might be able to use the additional params in the connect function.
import { push } from 'connected-react-router';
/* code stuffs */
props.push('/home');
export default connect(null, { push })(Component);
https://github.com/supasate/connected-react-router/blob/master/FAQ.md#with-react-redux

Redux property increment not working, if call through child component

I have flatlist having images, videos. for images, I defined duration to show and then move to the next item in flatlist, but in case of the video once a video ended then move to next item.
I am using Redux for currentlyPlayingIndex and flatlist datasource.
If I have only images in flatlist it is working fine, but if I have a video, on video end I need to pass event from child to its parent. The parent calls the same method to move to the next index as for image duration end but the increment of currentlyPlayingIndex by one is not happening in case of video end.
parent component code or flatlist handler
import React, { Component } from 'react'
import { TouchableWithoutFeedback, View,StatusBar,Dimensions,FlatList } from 'react-native'
import {FileType,getFileType,getFileExtension} from '../services/FileManagerService'
import VideoPlayer from '../components/PlayerTypes/VideoPlayer'
import ImagePlayer from '../components/PlayerTypes/ImagePlayer'
import PdfPlayer from '../components/PlayerTypes/PdfPlayer'
import { ScaledSheet } from 'react-native-size-matters';
import NothingTpPlay from '../components/PlayerTypes/NothingTpPlay'
//redux
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../database/actions/ScheduleActions.js'; //Import your actions
import * as Animatable from 'react-native-animatable';
import constants from '../config/constants'
import KeepAwake from 'react-native-keep-awake';
import AudioPlayer from '../components/PlayerTypes/AudioPlayer'
import { showToastMessage } from '../utils/ToastMessage'
import I18n from "../locales/i18n-js";
import WebsitePlayer from '../components/PlayerTypes/WebsitePlayer'
import FullScreen from "../NativeBridgingHeader/FullScreen";
let deviceWidth = Dimensions.get('window').width
let deviceHeight = Dimensions.get('window').height
class PlaylistPlayerScreen extends Component {
constructor() {
super();
this.state = {
currentVisibleIndex:-1
}
this.playNextFile = this.playNextFile.bind(this)
this.videoEnded = this.videoEnded.bind(this)
this.schedulePlayDurationTimer = this.schedulePlayDurationTimer.bind(this)
this.viewabilityConfig = {
waitForInteraction: false,
itemVisiblePercentThreshold: 99,
}
}
static navigationOptions = {
header: null,
};
onViewableItemsChanged = ({ viewableItems }) => {
// viewableItems will show you what items are in view
// console.log("onViewableItemsChanged called" + JSON.stringify(viewableItems))
if(viewableItems.length >= 1) {
const visibleFileIndex = viewableItems[0].index
// console.log("visible index " + visibleFileIndex)
this.setState({currentVisibleIndex:visibleFileIndex})
const file = this.props.schedulesFiles[visibleFileIndex]
const fileType = getFileType(file)
console.log("file type is " + fileType)
if (fileType == FileType.Video) {
console.log("video file type")
} else {
this.schedulePlayDurationTimer(visibleFileIndex)
}
}
}
getItemLayout = (data, index) => ({
length: deviceWidth,
offset: deviceWidth * index,
index,
})
componentDidMount(){
this.props.getScheduleFiles()
}
shouldComponentUpdate(nextProps, nextState) {
return true
}
componentDidUpdate(){
console.log("componentDidUpdate")
}
schedulePlayDurationTimer(file_index) {
const file = this.props.schedulesFiles[file_index]
const playDuration = file.play_duration_in_milliseconds
this.timer = setTimeout(() => {
clearTimeout(this.timer)
this.playNextFile()
}, playDuration);
}
videoEnded = () => {
console.log("video ended")
this.playNextFile()
}
playNextFile = () => {
if(this.props.currentlyPlayingIndex == (this.props.schedulesFiles.length - 1)) {
//last file played
this.props.getScheduleFiles()
this.props.playNextFile(this.props.schedulesFiles,this.props.currentlyPlayingIndex)
this.listRef.scrollToIndex({animated: false, index: this.props.currentlyPlayingIndex})
} else {
console.log("playNextFile current index " + this.props.currentlyPlayingIndex)
this.props.playNextFile(this.props.schedulesFiles,this.props.currentlyPlayingIndex)
console.log("playNextFile next index " + this.props.currentlyPlayingIndex)
this.listRef.scrollToIndex({animated: true, index: this.props.currentlyPlayingIndex})
}
}
_renderItem = ({item, index}) => {
return (
this.renderPlayer(item,index)
);
}
renderPlayer(file,index) {
switch (getFileType(file)) {
case FileType.Video:
return <VideoPlayer file={file} onEnd={this.videoEnded} currentIndex={index} currentVisibleIndex={this.state.currentVisibleIndex} />
case FileType.Audio:
return <AudioPlayer file={file} onEnd={this.playNextFile} />
case FileType.Image:
return <ImagePlayer file={file} onEnd={this.playNextFile} />
case FileType.Pdf:
return <PdfPlayer file={file} onEnd={this.playNextFile} />
case FileType.WebpageContent:
return <WebsitePlayer file={file} onEnd={this.playNextFile} />
default:
showToastMessage(
I18n.t('ErrorMessage.FormatNotSupported', {
name: getFileExtension(file).toUpperCase()
})
)
this.playNextFile()
}
}
render() {
if(this.props.schedulesFiles.length > 0 ) {
return (
<View style={{flex:1}}>
<StatusBar hidden={true} />
<FlatList
style={{flex:1}}
bounces={false}
removeClippedSubviews={true}
scrollEnabled={false}
showsHorizontalScrollIndicator={false}
ref={el => this.listRef = el}
horizontal={true}
keyExtractor={(item, index) => index.toString()}
data={this.props.schedulesFiles}
renderItem={this._renderItem}
onViewableItemsChanged={this.onViewableItemsChanged}
viewabilityConfig={this.viewabilityConfig}
getItemLayout={this.getItemLayout}
initialNumToRender={2}
maxToRenderPerBatch={2}
windowSize={this.props.schedulesFiles.length}
/>
<KeepAwake />
</View>
)
}else {
return (
<TouchableWithoutFeedback delayLongPress={constants.REVEAL_SIDE_BAR_MENU_PRESS_DURATION} onLongPress={() => this.props.navigation.openDrawer()}>
<View style={styles.container}>
<NothingTpPlay/>
<KeepAwake />
</View>
</TouchableWithoutFeedback>
)
}
}
}
const styles = ScaledSheet.create({
container: {
flex:1,
backgroundColor : 'white',
}
});
//redux binding
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.scheduleReducer.loading,
schedulesFiles: state.scheduleReducer.data,
currentlyPlayingIndex: state.scheduleReducer.nextFileIndex,
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/home.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(PlaylistPlayerScreen);
Render method for video player child component code is:
<VideoPlayer file={file} onEnd={this.videoEnded} currentIndex={index} currentVisibleIndex={this.state.currentVisibleIndex} />
VideoPlayer.js relevant code
export default class VideoPlayer extends React.Component {
constructor() {
super();
this.state = {
}
this.videoEnded = this.videoEnded.bind(this)
}
videoEnded() {
if (this.props.shouldRepeat == true) {
} else {
this.video.paused = true
this.video.seek(0)
}
this.props.onEnd()
}
render() {
return (
<Video
ref={ref => {
this.video = ref;
}}
onError={this.videoEnded}
minLoadRetryCount={1}
useTextureView={true}
controls={false}
style={ContainerStyle.playerTypeStyle}
onEnd={this.videoEnded}
repeat={this.props.shouldRepeat}
playInBackground={false}
playWhenInactive={false}
ignoreSilentSwitch={"ignore"}
resizeMode={this.props.file.resize_mode}
source={{uri:getFileAbsolutePath(this.props.file)}}
paused={this.props.currentIndex != this.props.currentVisibleIndex}
/>
)
}
}
Reducer code
import { combineReducers } from 'redux';
import { SCHEDULE_REFRESHED,PLAY_NEXT_FILE } from "../actions/ScheduleActions.js" //Import the actions types constant we defined in our actions
let dataState = { data: [], loading:true };
const scheduleReducer = (state = dataState, action) => {
switch (action.type) {
case SCHEDULE_REFRESHED:
state = Object.assign({}, state, { data: action.data, nextFileIndex:action.nextFileIndex });
return state;
case PLAY_NEXT_FILE:
state = Object.assign({}, state, { nextFileIndex: action.nextFileIndex});
return state;
default:
return state;
}
}
// Combine all the reducers
const rootReducer = combineReducers({
scheduleReducer
// ,[ANOTHER REDUCER], [ANOTHER REDUCER] ....
})
export default rootReducer;
Action code
//gets called initially on app launch to get files to be played
export function getScheduleFiles(){
return (dispatch) => {
getOfflineNextScheduleFiles().then((files)=>{//get offline files/schedule first
plainFiles = convertToArray(files)
dispatch({type: SCHEDULE_REFRESHED, data:plainFiles,nextFileIndex:0});
}).catch((error)=>{//if offline schedules is not available to play, refresh online
triggerPlaylistsRefresh().then((files)=>{
plainFiles = convertToArray(files)
dispatch({type: SCHEDULE_REFRESHED, data:plainFiles,nextFileIndex:0});
}).catch((error)=>{
console.log("nothing to play")
dispatch({type: PLAY_NEXT_FILE, nextFileIndex:0});
showToastMessage(I18n.t("ErrorMessage.NoSchedulesAvailableForCurrentTimeError"))
})
})
}
}
//get called from PlaylistPlayerScreen after each file played
export function playNextFile(files,filePlayedIndex){
return (dispatch) => {
if(filePlayedIndex < files.length-1) {
dispatch({type: PLAY_NEXT_FILE, nextFileIndex:filePlayedIndex+1});
}else {
console.log("all files played")
dispatch({type: PLAY_NEXT_FILE, nextFileIndex:0});
}
}
}

Virtual assistant created using Typscript- running in Bot Framework Emulator is not responding

I am trying to develop virtual assistant using typescript . i have followed this below document
https://microsoft.github.io/botframework-solutions/tutorials/typescript/create-assistant/1_intro/
When i run npm start and test it in Botframework emulator , the bot is not responding any message.
But the Bot is opening with new user greeting adaptive card message
I have tried to edit the adaptive greeting card following this document page
https://microsoft.github.io/botframework-solutions/tutorials/typescript/customize-assistant/2_edit_your_greeting/
but eventhough the bot is not replying any message
`[11:53:22]Emulator listening on http://localhost:49963`
[11:53:22]ngrok listening on https://b2915c2d.ngrok.io
[11:53:22]ngrok traffic inspector:http://127.0.0.1:4040
[11:53:22]Will bypass ngrok for local addresses
[11:53:23]<- messageapplication/vnd.microsoft.card.adaptive
[11:53:23]POST200conversations.replyToActivity
[11:53:23]POST200directline.conversationUpdate
[11:53:23]POST200directline.conversationUpdate
expected and actual results: it should ask what is your name once connected and start conversing
================================================================================mainDialog.ts
import {
BotFrameworkAdapter,
BotTelemetryClient,
RecognizerResult,
StatePropertyAccessor } from 'botbuilder';
import { LuisRecognizer, LuisRecognizerTelemetryClient, QnAMakerResult, QnAMakerTelemetryClient } from 'botbuilder-ai';
import {
DialogContext,
DialogTurnResult,
DialogTurnStatus } from 'botbuilder-dialogs';
import {
ISkillManifest,
SkillContext,
SkillDialog,
SkillRouter } from 'botbuilder-skills';
import {
ICognitiveModelSet,
InterruptionAction,
RouterDialog,
TokenEvents } from 'botbuilder-solutions';
import { TokenStatus } from 'botframework-connector';
import {
Activity,
ActivityTypes } from 'botframework-schema';
import i18next from 'i18next';
import { IOnboardingState } from '../models/onboardingState';
import { CancelResponses } from '../responses/cancelResponses';
import { MainResponses } from '../responses/mainResponses';
import { BotServices } from '../services/botServices';
import { IBotSettings } from '../services/botSettings';
import { CancelDialog } from './cancelDialog';
import { EscalateDialog } from './escalateDialog';
import { OnboardingDialog } from './onboardingDialog';
enum Events {
timeZoneEvent = 'va.timeZone',
locationEvent = 'va.location'
}
export class MainDialog extends RouterDialog {
// Fields
private readonly luisServiceGeneral: string = 'general';
private readonly luisServiceFaq: string = 'faq';
private readonly luisServiceChitchat: string = 'chitchat';
private readonly settings: Partial<IBotSettings>;
private readonly services: BotServices;
private readonly skillContextAccessor: StatePropertyAccessor<SkillContext>;
private readonly onboardingAccessor: StatePropertyAccessor<IOnboardingState>;
private readonly responder: MainResponses = new MainResponses();
// Constructor
public constructor(
settings: Partial<IBotSettings>,
services: BotServices,
onboardingDialog: OnboardingDialog,
escalateDialog: EscalateDialog,
cancelDialog: CancelDialog,
skillDialogs: SkillDialog[],
skillContextAccessor: StatePropertyAccessor<SkillContext>,
onboardingAccessor: StatePropertyAccessor<IOnboardingState>,
telemetryClient: BotTelemetryClient
) {
super(MainDialog.name, telemetryClient);
this.settings = settings;
this.services = services;
this.onboardingAccessor = onboardingAccessor;
this.skillContextAccessor = skillContextAccessor;
this.telemetryClient = telemetryClient;
this.addDialog(onboardingDialog);
this.addDialog(escalateDialog);
this.addDialog(cancelDialog);
skillDialogs.forEach((skillDialog: SkillDialog): void => {
this.addDialog(skillDialog);
});
}
protected async onStart(dc: DialogContext): Promise<void> {
const view: MainResponses = new MainResponses();
const onboardingState: IOnboardingState|undefined = await this.onboardingAccessor.get(dc.context);
if (onboardingState === undefined || onboardingState.name === undefined || onboardingState.name === '') {
await view.replyWith(dc.context, MainResponses.responseIds.newUserGreeting);
} else {
await view.replyWith(dc.context, MainResponses.responseIds.returningUserGreeting);
}
}
protected async route(dc: DialogContext): Promise<void> {
// Get cognitive models for locale
const locale: string = i18next.language.substring(0, 2);
const cognitiveModels: ICognitiveModelSet | undefined = this.services.cognitiveModelSets.get(locale);
if (cognitiveModels === undefined) {
throw new Error('There is no value in cognitiveModels');
}
// Check dispatch result
const dispatchResult: RecognizerResult = await cognitiveModels.dispatchService.recognize(dc.context);
const intent: string = LuisRecognizer.topIntent(dispatchResult);
if (this.settings.skills === undefined) {
throw new Error('There is no skills in settings value');
}
// Identify if the dispatch intent matches any Action within a Skill if so, we pass to the appropriate SkillDialog to hand-off
const identifiedSkill: ISkillManifest | undefined = SkillRouter.isSkill(this.settings.skills, intent);
if (identifiedSkill !== undefined) {
// We have identified a skill so initialize the skill connection with the target skill
const result: DialogTurnResult = await dc.beginDialog(identifiedSkill.id);
if (result.status === DialogTurnStatus.complete) {
await this.complete(dc);
}
} else if (intent === 'l_general') {
// If dispatch result is general luis model
const luisService: LuisRecognizerTelemetryClient | undefined = cognitiveModels.luisServices.get(this.luisServiceGeneral);
if (luisService === undefined) {
throw new Error('The specified LUIS Model could not be found in your Bot Services configuration.');
} else {
const result: RecognizerResult = await luisService.recognize(dc.context);
if (result !== undefined) {
const generalIntent: string = LuisRecognizer.topIntent(result);
// switch on general intents
switch (generalIntent) {
case 'Escalate': {
// start escalate dialog
await dc.beginDialog(EscalateDialog.name);
break;
}
case 'None':
default: {
// No intent was identified, send confused message
await this.responder.replyWith(dc.context, MainResponses.responseIds.confused);
}
}
}
}
} else if (intent === 'q_faq') {
const qnaService: QnAMakerTelemetryClient | undefined = cognitiveModels.qnaServices.get(this.luisServiceFaq);
if (qnaService === undefined) {
throw new Error('The specified QnA Maker Service could not be found in your Bot Services configuration.');
} else {
const answers: QnAMakerResult[] = await qnaService.getAnswers(dc.context);
if (answers !== undefined && answers.length > 0) {
await dc.context.sendActivity(answers[0].answer, answers[0].answer);
} else {
await this.responder.replyWith(dc.context, MainResponses.responseIds.confused);
}
}
} else if (intent === 'q_chitchat') {
const qnaService: QnAMakerTelemetryClient | undefined = cognitiveModels.qnaServices.get(this.luisServiceChitchat);
if (qnaService === undefined) {
throw new Error('The specified QnA Maker Service could not be found in your Bot Services configuration.');
} else {
const answers: QnAMakerResult[] = await qnaService.getAnswers(dc.context);
if (answers !== undefined && answers.length > 0) {
await dc.context.sendActivity(answers[0].answer, answers[0].answer);
} else {
await this.responder.replyWith(dc.context, MainResponses.responseIds.confused);
}
}
} else {
// If dispatch intent does not map to configured models, send 'confused' response.
await this.responder.replyWith(dc.context, MainResponses.responseIds.confused);
}
}
protected async onEvent(dc: DialogContext): Promise<void> {
// Check if there was an action submitted from intro card
if (dc.context.activity.value) {
// tslint:disable-next-line: no-unsafe-any
if (dc.context.activity.value.action === 'startOnboarding') {
await dc.beginDialog(OnboardingDialog.name);
return;
}
}
let forward: boolean = true;
const ev: Activity = dc.context.activity;
if (ev.name !== undefined && ev.name.trim().length > 0) {
switch (ev.name) {
case Events.timeZoneEvent: {
try {
const timezone: string = <string> ev.value;
const tz: string = new Date().toLocaleString(timezone);
const timeZoneObj: {
timezone: string;
} = {
timezone: tz
};
const skillContext: SkillContext = await this.skillContextAccessor.get(dc.context, new SkillContext());
skillContext.setObj(timezone, timeZoneObj);
await this.skillContextAccessor.set(dc.context, skillContext);
} catch {
await dc.context.sendActivity(
{
type: ActivityTypes.Trace,
text: `"Timezone passed could not be mapped to a valid Timezone. Property not set."`
}
);
}
forward = false;
break;
}
case Events.locationEvent: {
const location: string = <string> ev.value;
const locationObj: {
location: string;
} = {
location: location
};
const skillContext: SkillContext = await this.skillContextAccessor.get(dc.context, new SkillContext());
skillContext.setObj(location, locationObj);
await this.skillContextAccessor.set(dc.context, skillContext);
forward = true;
break;
}
case TokenEvents.tokenResponseEventName: {
forward = true;
break;
}
default: {
await dc.context.sendActivity(
{
type: ActivityTypes.Trace,
text: `"Unknown Event ${ ev.name } was received but not processed."`
}
);
forward = false;
}
}
}
if (forward) {
const result: DialogTurnResult = await dc.continueDialog();
if (result.status === DialogTurnStatus.complete) {
await this.complete(dc);
}
}
}
protected async complete(dc: DialogContext, result?: DialogTurnResult): Promise<void> {
// The active dialog's stack ended with a complete status
await this.responder.replyWith(dc.context, MainResponses.responseIds.completed);
}
protected async onInterruptDialog(dc: DialogContext): Promise<InterruptionAction> {
if (dc.context.activity.type === ActivityTypes.Message) {
const locale: string = i18next.language.substring(0, 2);
const cognitiveModels: ICognitiveModelSet | undefined = this.services.cognitiveModelSets.get(locale);
if (cognitiveModels === undefined) {
throw new Error('There is no cognitiveModels value');
}
// check luis intent
const luisService: LuisRecognizerTelemetryClient | undefined = cognitiveModels.luisServices.get(this.luisServiceGeneral);
if (luisService === undefined) {
throw new Error('The general LUIS Model could not be found in your Bot Services configuration.');
} else {
const luisResult: RecognizerResult = await luisService.recognize(dc.context);
const intent: string = LuisRecognizer.topIntent(luisResult);
// Only triggers interruption if confidence level is high
if (luisResult.intents[intent] !== undefined && luisResult.intents[intent].score > 0.5) {
switch (intent) {
case 'Cancel': {
return this.onCancel(dc);
}
case 'Help': {
return this.onHelp(dc);
}
case 'Logout': {
return this.onLogout(dc);
}
default:
}
}
}
}
return InterruptionAction.NoAction;
}
private async onCancel(dc: DialogContext): Promise<InterruptionAction> {
if (dc.activeDialog !== undefined && dc.activeDialog.id !== CancelDialog.name) {
// Don't start restart cancel dialog
await dc.beginDialog(CancelDialog.name);
// Signal that the dialog is waiting on user response
return InterruptionAction.StartedDialog;
}
const view: CancelResponses = new CancelResponses();
await view.replyWith(dc.context, CancelResponses.responseIds.nothingToCancelMessage);
return InterruptionAction.StartedDialog;
}
private async onHelp(dc: DialogContext): Promise<InterruptionAction> {
await this.responder.replyWith(dc.context, MainResponses.responseIds.help);
// Signal the conversation was interrupted and should immediately continue
return InterruptionAction.MessageSentToUser;
}
private async onLogout(dc: DialogContext): Promise<InterruptionAction> {
let adapter: BotFrameworkAdapter;
const supported: boolean = dc.context.adapter instanceof BotFrameworkAdapter;
if (!supported) {
throw new Error('OAuthPrompt.SignOutUser(): not supported by the current adapter');
} else {
adapter = <BotFrameworkAdapter> dc.context.adapter;
}
await dc.cancelAllDialogs();
// Sign out user
// PENDING check adapter.getTokenStatusAsync
const tokens: TokenStatus[] = [];
tokens.forEach(async (token: TokenStatus): Promise<void> => {
if (token.connectionName !== undefined) {
await adapter.signOutUser(dc.context, token.connectionName);
}
});
await dc.context.sendActivity(i18next.t('main.logOut'));
return InterruptionAction.StartedDialog;
}
}
=================================================================================onboardingDialog.ts
import {
BotTelemetryClient,
StatePropertyAccessor,
TurnContext } from 'botbuilder';
import {
ComponentDialog,
DialogTurnResult,
TextPrompt,
WaterfallDialog,
WaterfallStepContext } from 'botbuilder-dialogs';
import { IOnboardingState } from '../models/onboardingState';
import { OnboardingResponses } from '../responses/onboardingResponses';
import { BotServices } from '../services/botServices';
enum DialogIds {
namePrompt = 'namePrompt',
emailPrompt = 'emailPrompt',
locationPrompt = 'locationPrompt'
}
export class OnboardingDialog extends ComponentDialog {
// Fields
private static readonly responder: OnboardingResponses = new OnboardingResponses();
private readonly accessor: StatePropertyAccessor<IOnboardingState>;
private state!: IOnboardingState;
// Constructor
public constructor(botServices: BotServices, accessor: StatePropertyAccessor<IOnboardingState>, telemetryClient: BotTelemetryClient) {
super(OnboardingDialog.name);
this.accessor = accessor;
this.initialDialogId = OnboardingDialog.name;
const onboarding: ((sc: WaterfallStepContext<IOnboardingState>) => Promise<DialogTurnResult>)[] = [
this.askForName.bind(this),
this.finishOnboardingDialog.bind(this)
];
// To capture built-in waterfall dialog telemetry, set the telemetry client
// to the new waterfall dialog and add it to the component dialog
this.telemetryClient = telemetryClient;
this.addDialog(new WaterfallDialog(this.initialDialogId, onboarding));
this.addDialog(new TextPrompt(DialogIds.namePrompt));
}
public async askForName(sc: WaterfallStepContext<IOnboardingState>): Promise<DialogTurnResult> {
this.state = await this.getStateFromAccessor(sc.context);
if (this.state.name !== undefined && this.state.name.trim().length > 0) {
return sc.next(this.state.name);
}
return sc.prompt(DialogIds.namePrompt, {
prompt: await OnboardingDialog.responder.renderTemplate(
sc.context,
OnboardingResponses.responseIds.namePrompt,
<string> sc.context.activity.locale)
});
}
public async finishOnboardingDialog(sc: WaterfallStepContext<IOnboardingState>): Promise<DialogTurnResult> {
this.state = await this.getStateFromAccessor(sc.context);
this.state.name = <string> sc.result;
await this.accessor.set(sc.context, this.state);
await OnboardingDialog.responder.replyWith(
sc.context,
OnboardingResponses.responseIds.haveNameMessage,
{
name: this.state.name
});
return sc.endDialog();
}
private async getStateFromAccessor(context: TurnContext): Promise<IOnboardingState> {
const state: IOnboardingState | undefined = await this.accessor.get(context);
if (state === undefined) {
const newState: IOnboardingState = {
email: '',
location: '',
name: ''
};
await this.accessor.set(context, newState);
return newState;
}
return state;
}
}
=================================================================================dialogBot.ts
import {
ActivityHandler,
BotTelemetryClient,
ConversationState,
EndOfConversationCodes,
Severity,
TurnContext } from 'botbuilder';
import {
Dialog,
DialogContext,
DialogSet,
DialogState } from 'botbuilder-dialogs';
export class DialogBot<T extends Dialog> extends ActivityHandler {
private readonly telemetryClient: BotTelemetryClient;
private readonly solutionName: string = 'samplevirtualassistant';
private readonly rootDialogId: string;
private readonly dialogs: DialogSet;
public constructor(
conversationState: ConversationState,
telemetryClient: BotTelemetryClient,
dialog: T) {
super();
this.rootDialogId = dialog.id;
this.telemetryClient = telemetryClient;
this.dialogs = new DialogSet(conversationState.createProperty<DialogState>(this.solutionName));
this.dialogs.add(dialog);
this.onTurn(this.turn.bind(this));
}
// eslint-disable-next-line #typescript-eslint/no-explicit-any, #typescript-eslint/tslint/config
public async turn(turnContext: TurnContext, next: () => Promise<void>): Promise<any> {
// Client notifying this bot took to long to respond (timed out)
if (turnContext.activity.code === EndOfConversationCodes.BotTimedOut) {
this.telemetryClient.trackTrace({
message: `Timeout in ${ turnContext.activity.channelId } channel: Bot took too long to respond`,
severityLevel: Severity.Information
});
return;
}
const dc: DialogContext = await this.dialogs.createContext(turnContext);
if (dc.activeDialog !== undefined) {
await dc.continueDialog();
} else {
await dc.beginDialog(this.rootDialogId);
}
await next();
}
}

Resources