angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.37k stars 6.75k forks source link

(suggestion)md-table with http example #5670

Closed alanpurple closed 7 years ago

alanpurple commented 7 years ago

Bug, feature request, or proposal:

proposal

What is the expected behavior?

something intuitive

What is the current behavior?

no idea how to

It will be very thankful if you provide an example of md-table with http service, not some mock database class

egavard commented 7 years ago

Meanwhile the documentation is updated, I can provide you my own implementation with an HTTP REST API.

Here, I retrieve some Conventions from my API and send them to the table. There's not filter, nor sorting and every data is sent to the view (as I only have a few ones).

export class DelegateConventionDatasource extends DataSource<Convention>{

  delegate:Delegate;
  subject:BehaviorSubject<Convention[]>;

//I have a custom http component that adds headers on calls
  constructor(private authHttpService:AuthHttpService, private userLoginService:UserLoginService){
    super();
  }

  connect(collectionViewer: CollectionViewer): Observable<Convention[]> {
    this.subject = new BehaviorSubject<Convention[]>([]);
//My http call relies on a property stored in this service, so I watch changes on it
    this.userLoginService.userChange.subscribe((user) => {
      this.delegate = user.delegate;
//I prevent http calls when subject is supposed to be disconnected (route to another link and back there)
      if(!this.subject.isStopped){
        this.retrieveConventions(this.delegate.id).then((response:Response) => {
          const conventions: Convention[] = response.json();
          this.subject.next(conventions);
        });
      }
    });

    return Observable.merge(this.subject);

  }

  retrieveConventions(delegateId:number|string):Promise<Response>{
    return this.authHttpService.get(`${environment.api_url}delegate/${delegateId}/convention`);
  }

  disconnect(collectionViewer: CollectionViewer): void {
    this.subject.complete();
    this.subject.observers = [];
  }

Here's an example without my logic :

export class DelegateConventionDatasource extends DataSource<Convention>{

  subject:BehaviorSubject<Convention[]>;

  constructor(private http:Http){
    super();
  }

  connect(collectionViewer: CollectionViewer): Observable<Convention[]> {
    this.subject = new BehaviorSubject<Convention[]>([]);
     this.http.get('http://someurl.com/conventions').toPromise()
      .then((response:Response) => {
        const conventions: Convention[] = response.json();
        this.subject.next(conventions);
      });
    return Observable.merge(this.subject);
  }

  disconnect(collectionViewer: CollectionViewer): void {
    this.subject.complete();
    this.subject.observers = [];
  }
alanpurple commented 7 years ago

@egavard thanks, I tried what you provided, but

even connect() wasn't called

export class ItemDataSource extends DataSource<Item>{
    constructor(private http: Http) {
        super();
    }

    subject: BehaviorSubject<Item[]> = new BehaviorSubject<Item[]>([]);

    connect(): Observable<Item[]> {
        console.log('connect');
        if (!this.subject.isStopped)
            this.http.get('/item').toPromise()
                .then(res => {
                    this.subject.next(res.json())
                });
        return Observable.merge(this.subject);
    }

    disconnect() {
        this.subject.complete();
        this.subject.observers = [];
    }
}
 ngOnInit() {
        this.itemDataSource = new ItemDataSource(this.http);
    }
<md-table [dataSource]="itemDataSource">
    <ng-container cdkColumnDef="name">
        <md-header-cell *cdkHeaderCellDef>이름</md-header-cell>
        <md-cell *cdkCellDef="let row">{{row.name}}</md-cell>
    </ng-container>
    <md-header-row *cdkHeaderRowDef="['name']"></md-header-row>
    <md-row *cdkRowDef="let row; columns: ['name'];"></md-row>
</md-table>

with no error

egavard commented 7 years ago

Maybe it's because your connect and disconnect methods don't match the needed signature with CollectionViewer ?

Could you try ?

EDIT: That's not causing trouble. The only thing I can see that differs from my sources is that I'm instanciating the datasource in my constructor and not onInit.

alanpurple commented 7 years ago

@egavard already tried

alanpurple commented 7 years ago

@egavard thanks, it now works, for some reason, after restart chrome...

alanpurple commented 7 years ago

@egavard may I ask why you use .toPromise ? instead of standard map and catch

egavard commented 7 years ago

@alanpurple It's just a matter of preferences. I don't like Observable. And in this case, I don't need abort, nor retries.

jelbourn commented 7 years ago

@andrewseguin We should have an example http-based data source in the docs.

alanpurple commented 7 years ago

I think in much of http cases, BehaviorSubject is not required

@Injectable()
export class ItemService {
    constructor(private http: Http) { }

    getItemAll(): Observable<Item[]> {
        return this.http.get('/item')
            .map(res => res.json())
            .catch(err => Observable.throw(err));
    }
...
}
export class ItemDataSource extends DataSource<Item>{
    constructor(private itemService: ItemService) {
        super();
    }

    connect(): Observable<Item[]> {
        return this.itemService.getItemAll();
    }

    disconnect() {
    }
}

this is enough

egavard commented 7 years ago

Did you try to navigate between another component and the one using the table ? Is the http call only made once each time ?

That was the reason I used the BehaviorSubject. It allowed me to mark the subject complete on disconnect, to prevent multiple calls to my API.

alanpurple commented 7 years ago

@egavard yes. exactly same, once each time

andrewseguin commented 7 years ago

Here's an example we will put up on the docs site soon: https://plnkr.co/edit/mjQbufh7cUynD6qhF5Ap?p=preview

irowbin commented 7 years ago

How can i fix the header when scroll? Anyone?

duard commented 7 years ago

Very hard to apply pagination, sort and filter

lemang commented 7 years ago

Can someone show how to apply http pagination in the table?

irowbin commented 7 years ago

Hey @lemang Here is my code. 👨‍💻

  // my http service  for data source
   getClaims(param?: { page?: number, pageSize?: number }): Observable<MyDtoModel> {

       // just creating params
        let params = this.url.paramsWithValue(param);

        return this.http
            .get(`${this.api}/Get`, this.url.requestOptions(null, params)) // my custom header options.
            .map(this.dataHelper.extractData)
            .catch(this.dataHelper.handleErrors);
    }

   // component.ts
    displayedColumns = ['col1', 'col2', 'col3', 'col4', 'col5'];
    dataSource: DataSource<IClaimResources>;
    @ViewChild(MdPaginator) paginator: MdPaginator;
    @ViewChild(MdSort) sort: MdSort;

      constructor( private ClaimService: ClaimService) { }

      ngOnInit() {

         // refactor is welcome.
           this.dataSource =  new DelegateClaimsDatasource(this.ClaimService, this.paginator, this.sort);   

      }

// datasource delegate class
export class DelegateClaimsDatasource extends DataSource<IClaimResources> {

    subject: BehaviorSubject<IClaimResources[]>;
    pager: IPager;  //my server dto models

    constructor(private claimService: ClaimService, private paginator: MdPaginator, private sort: MdSort) {
        super();
    }

     private getData(p: MdPaginator, claimService: ClaimService) {
        claimService.getClaims({ page: p.pageIndex, pageSize: p.pageSize })
            .map((res: IHttpApiMessage) => <DtoModel>res.contentBody)
            .do((dto: DtoModel) => this.subject.next(dto.content))
            .subscribe((dto: DtoModel) => this.pager = dto.pager, (error: any) => Observable.throw(error));
     }

    // refactoring is welcome.
    connect(): Observable<IClaimResources[]> {

        const displayedChanges = [
            this.sort.mdSortChange,
            this.paginator.page,
        ];

        // on changes
        Observable.merge(...displayedChanges).subscribe((d) => {

            // if it is sorting, reset page index to 0;
            if (d['active'] && d['direction']) this.paginator.pageIndex = 0;

            this.getData(this.paginator, this.claimService, this.sort);
        });

       // init
        if (!this.subject.isStopped)
            this.getData(this.paginator, this.claimService, this.sort, );

        return Observable.merge(this.subject);
    }

    disconnect(): void {

        this.subject.complete();
        this.subject.observers = [];
    }
}

// component.html

 <md-table  [dataSource]="dataSource" mdSort >
    // md-row.... same as angular docs.
 <md-table>
// paginator... My paging calculation is dynamic.
 <md-paginator [pageSizeOptions]="[5, 10, 15, 35, 50, 100, 200]"
        [length]="dataSource?.pager?.totalItems" [pageIndex]="dataSource?.pager?.currentPage" [pageSize]="dataSource?.pager?.pageSize">
 </md-paginator>
nnarayanancs commented 7 years ago

@irowbin hi how did you make the checkbox work for each row. When i select a checkbox it selects all the checkboxes.

irowbin commented 7 years ago

@nnarayanancs Here https://plnkr.co/edit/EU3BBlViWpPf2NJW4PXx?p=preview

nnarayanancs commented 7 years ago

I am getting this error ERROR in SelectionModel is not an NgModule even though I have imported SelectionModel from angular material. I am already using md-table from the material. So I know it is installed. Please advise.

From: Robin Regmi [mailto:notifications@github.com] Sent: Monday, July 31, 2017 9:31 PM To: angular/material2 material2@noreply.github.com Cc: Nisha Narayanan nnarayanan@casestack.com; Mention mention@noreply.github.com Subject: Re: [angular/material2] (suggestion)md-table with http example (#5670)

@nnarayanancshttps://github.com/nnarayanancs Here https://plnkr.co/edit/EU3BBlViWpPf2NJW4PXx?p=preview

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/angular/material2/issues/5670#issuecomment-319264709, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AcyOlWx-_XeOn_MdADCSHnoYrJE9vMJAks5sTqoOgaJpZM4OTzD_.

DavidTheProgrammer commented 7 years ago

@irowbin Why are you importing the "startWith" rx operator? It's not used anywhere in the code?

egavard commented 7 years ago

Could you please focus on the original subject here, the http datasource example ?

irowbin commented 7 years ago

@DavidTheProgrammer That wasn't my code. I just post here to help for checkbox selection. @egavard Yeah you're right. Focus the actual Subject.

TejasCMehta commented 7 years ago

Can any one has working plunker for server side pagination,sorting,filtering? I am trying like below approach:

CategoryService which will provide paged data. CategoryListing.ts file which will handle all data, sorting, paging and filtering. CategoryListing.html to display category data and edit button.

@irowbin : I looked into your plunker but I did not find service file over there as you have , please guide me in that.

Thanks,

Braincompiler commented 7 years ago

I made an small and runnable example project where the backend is remote, the state manager ist ngrx and sort and pagination just triggers the request actions. Even filtering can be implemented this way. https://github.com/Braincompiler/ngrx-material-remote-data-table Hope this helps someone :)

ghost commented 7 years ago

Is there something special that needs to be done for sorting? My table shows up fine initially, but after trying to sort with a mat-sort-header column, I get the following error:

Uncaught Error: Error trying to diff '[object Object]'. Only arrays and iterables are allowed at DefaultIterableDiffer.webpackJsonp.../../../core/@angular/core.es5.js.DefaultIterableDiffer.diff (core.es5.js:6843) at CdkTable.webpackJsonp.../../../cdk/esm5/table.es5.js.CdkTable._renderRowChanges (table.es5.js:633) at SafeSubscriber._next (table.es5.js:605) at SafeSubscriber.webpackJsonp.../../../../rxjs/Subscriber.js.SafeSubscriber.__tryOrUnsub (Subscriber.js:238) at SafeSubscriber.webpackJsonp.../../../../rxjs/Subscriber.js.SafeSubscriber.next (Subscriber.js:185) at Subscriber.webpackJsonp.../../../../rxjs/Subscriber.js.Subscriber._next (Subscriber.js:125) at Subscriber.webpackJsonp.../../../../rxjs/Subscriber.js.Subscriber.next (Subscriber.js:89) at TakeUntilSubscriber.webpackJsonp.../../../../rxjs/Subscriber.js.Subscriber._next (Subscriber.js:125) at TakeUntilSubscriber.webpackJsonp.../../../../rxjs/Subscriber.js.Subscriber.next (Subscriber.js:89) at MapSubscriber.webpackJsonp.../../../../rxjs/operators/map.js.MapSubscriber._next (map.js:85)

andrewseguin commented 7 years ago

@sneckelmann Looks like your data source offered an object to the table through the connect stream. Make sure you only emit arrays of data on the stream.

ghost commented 7 years ago

Thanks for the feedback @andrewseguin . I am returning an Observable of an object array. It works the first time for initial load but sorting causes the mentioned error.

I'll keep trying to hunt down an answer though but seems I may end up on SO.

Thanks again.

andrewseguin commented 7 years ago

@sneckelmann My assumption is that you merged several streams together and the result is assumed to be your data. E.g. you merged dataChanges and sortChanges - when dataChanges emits, you receive your data, but when sortChanges emits then you receive the sort change event which is what you are returning to the table.

If that's not it, for sure post the data source on StackOverflow, there's a great community there that has been answering a lot of similar questions. Observables are awesome but they have a steeper learning curve.

ghost commented 7 years ago

Here's an example we will put up on the docs site soon: https://plnkr.co/edit/mjQbufh7cUynD6qhF5Ap?p=preview

@andrewseguin - thanks for this. Would you be able to add sorting, filtering, and pagination on this example, too? I think there's a few of us, myself included, that are facing issues with this.

pevans360 commented 7 years ago

A working http example would be very helpful for me as well.

Tried to run the plunkr above and got:

Event tracked Multipane Show Preview Toolbar undefined undefined editor-1.2.1.js:2 URL visited /edit/mjQbufh7cUynD6qhF5Ap?p=preview unpkg.com/@angular/cdk@2.0.0-beta.12/bundles/cdk.umd.js/bidi:1 GET https://unpkg.com/@angular/cdk@2.0.0-beta.12/bundles/cdk.umd.js/bidi 404 () zone.min.js:1 Error: (SystemJS) XHR error (404) loading https://unpkg.com/@angular/cdk/bundles/cdk.umd.js/bidi Error: XHR error (404) loading https://unpkg.com/@angular/cdk/bundles/cdk.umd.js/bidi at new k (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:12526) at XMLHttpRequest.t [as __zone_symbol___onreadystatechange] (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:881) at e.invokeTask (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:20606) at t.runTask (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:15783) at XMLHttpRequest.invoke (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:21563) Error loading https://unpkg.com/@angular/cdk/bundles/cdk.umd.js/bidi as "@angular/cdk/bidi" from https://unpkg.com/@angular/material/bundles/material.umd.js at new k (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:12526) at XMLHttpRequest.t [as __zone_symbol___onreadystatechange] (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:881) at e.invokeTask (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:20606) at t.runTask (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:15783) at XMLHttpRequest.invoke (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:21563) Error loading https://unpkg.com/@angular/cdk/bundles/cdk.umd.js/bidi as "@angular/cdk/bidi" from https://unpkg.com/@angular/material/bundles/material.umd.js etc.

iamwhitebox commented 7 years ago

I am working diligently on an example with http, sorting, pagination - I'm almost there but was getting errors where the sort, paginator components were trying to render before dataSource.filteredData was available.

I tried moving everything to ngAfterViewInit to no success as well. I'll update shortly!

Braincompiler commented 7 years ago

@pevans360 @iamwhitebox is my example not helpful? :) https://github.com/Braincompiler/ngrx-material-remote-data-table

Do you miss something?

marinantonio commented 7 years ago

project

I started with angular few weeks ago (I have 0 .js experience in general) and this is how it looks so far. Add and Edit are working using mat dialogs. Implementing Delete shouldn't be a problem aswell. Few issues tho:

  1. I have no idea how to update view in table after I close dialog. At the moment when I close dialog I'm updating entire table with http 'getAll' function. Hassling with dataSource looks way out of my beginner league. :)

  2. I still have to migrate from Http to HttpClient and I kinda have some issues with getAll.

When I sort this issues I can upload entire project or if someone can help I can post it now.

PS: Sorry for Croatian on image but u get an idea. Oh and pagination works aswell.

azimuthdeveloper commented 7 years ago

After trying for days to make this work, I'm ripping it out in favour of a static template displaying the information coming back. Is there an option in Angular Material, maybe, to implement a table which just loads from static data? If we could load a strongly typed result set into a table and have that displayed, that would be awesome. Maybe rename the existing mat-table to mat-table-dynamic (or add a dynamic operator to the tag or something, I don't know). For simple/most workloads, that would suffice. The existing mat-table implementation is extremely confusing and needlessly so.

But I love Angular Material and thank you all for the hard work you do and continue to do on this amazing framework!

marinantonio commented 6 years ago

Okay I'm almost done, but still stuck with the first thing. Here's an Stackoverflow question if someone more experienced could take a look:

https://stackoverflow.com/questions/47443582/angular-material-table-is-it-possible-to-update-rows-without-entire-table-ref

1antares1 commented 6 years ago

I understand that the topic is already closed, but I would like to share a missing "feature": Server-side Filtering.

I will share my full src (Angular 4) based on the @irowbin code (thanks).

tuple.ts model

export interface ITuple<T> {
    value: T;
    text: any;
}

export class Tuple<T> implements ITuple<T> {
    constructor(
        public value: T,
        public text: any
    ) { }
}

component.html

 <div class="example-header">
            <mat-form-field>
                <input matInput #inputFilterValue placeholder="Filter">
            </mat-form-field>
        </div>
        <mat-table class="app-nna-table" #table [dataSource]="dataSource" matSort>

            <ng-container matColumnDef="productName">
                <mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
                <mat-cell *matCellDef="let element"> {{element.productName || 'No available'}} </mat-cell>
            </ng-container>

            <ng-container matColumnDef="description">
                <mat-header-cell *matHeaderCellDef mat-sort-header> Description </mat-header-cell>
                <mat-cell *matCellDef="let element"> {{element.description || 'No available'}} </mat-cell>
            </ng-container>

            <ng-container matColumnDef="productTypeName">
                <mat-header-cell *matHeaderCellDef mat-sort-header> Category </mat-header-cell>
                <mat-cell *matCellDef="let element"> {{element.productTypeName}} </mat-cell>
            </ng-container>

            <ng-container matColumnDef="validFromDate">
                <mat-header-cell *matHeaderCellDef mat-sort-header> Start </mat-header-cell>
                <mat-cell *matCellDef="let element"> {{(element.validFromDate | date) || 'No available'}} </mat-cell>
            </ng-container>

            <ng-container matColumnDef="validToDate">
                <mat-header-cell *matHeaderCellDef mat-sort-header> End </mat-header-cell>
                <mat-cell *matCellDef="let element"> {{(element.validToDate | date) || 'No available'}} </mat-cell>
            </ng-container>

            <ng-container matColumnDef="actions">
                <mat-header-cell *matHeaderCellDef mat-sort-header> Actions </mat-header-cell>
                <mat-cell *matCellDef="let element">
                    <div class="app-tablet-actions">
                        <a href="">
                            <mat-icon title="Add Product">add_circle</mat-icon>
                        </a>
                        <a href="">
                            <mat-icon>mode_edit</mat-icon>
                        </a>
                        <a href="">
                            <mat-icon>delete</mat-icon>
                        </a>
                    </div>
                </mat-cell>
            </ng-container>

            <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
            <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
        </mat-table>
        <mat-paginator #paginator [length]="dataSource?.pager?.totalCount" [pageIndex]="dataSource?.pager?.requestedPage" [pageSize]="10" [pageSizeOptions]="[5, 10, 25, 50, 100]">
        </mat-paginator>

data-source.ts

export class MatTableDataSource<T> extends DataSource<any[]> {
    //#region properties
    public pager: any[];
    //#endregion

    constructor(
        public resource: RESTfulangular<T>,
        public subject: BehaviorSubject<any[]>,
        public paginator: MatPaginator,
        public sort: MatSort,
        public filters: ITuple<any>
    ) {
        super();
    }

    public connect(): Observable<any[]> {
        //#region properties
        const displayedChanges = [
            this.sort.sortChange,
            this.paginator.page,
            this.filters.text
        ];
        //#endregion

        //#region logic
        // on changes
        Observable.merge(...displayedChanges).subscribe((d: {}|Element) => {
            // if it's sorting, reset page index to 0;
            if ((d as any)["active"] && (d as any)["direction"]) {
                this.paginator.pageIndex = 0;
            }
            this.getData(this.resource, this.paginator, this.sort, ((d instanceof KeyboardEvent) ? (d.target as HTMLInputElement).value : null));
        });

        // init
        if (!this.subject.isStopped) {
            this.getData(this.resource, this.paginator, this.sort, null);
        }
        //#endregion

        return Observable.merge(this.subject);
    }

    public disconnect(): void {
        this.subject.complete();
        this.subject.observers = [];
    }

    // RESTfulangular is a custom model for ngx-restangular
    private getData(resource: RESTfulangular<T>, paginator: MatPaginator, sort: MatSort, filterValue?: string) {
        const callbackRequest = (params?: any): void => {
            resource.customGET(null, params)
            .map((metadata: any) => metadata)
            .do((elements: any) => this.subject.next(elements.items))
            .subscribe((dto: IProduct[]) => {
                this.pager = dto;
            }, (err: any) => Observable.throw(err));
        };

        const urlParams: IUrlParams = {
            page: paginator.pageIndex || 1,
            count: paginator.pageSize || 10,
            orderBy: sort.active,
            descending: (sort.direction === "desc") ? true : false,
        } as IUrlParams;
        const filterParams: ITuple<any> = {} as ITuple<any>;

        // can be improved (refactoring accepted)
        if (filterValue && this.filters && this.filters.value && this.filters.value.length) {
            this.filters.value.forEach((column: string) => {
                (filterParams as any)[column] = filterValue;
            });
        }
        callbackRequest((filterParams) ? Object.assign(urlParams, filterParams) : urlParams);
     }
}

--

component.ts

export class ProductsComponent implements OnInit, AfterViewInit {
    //#region properties
    // public dataSource = new MatTableDataSource<Element>(null); => NgMaterial 5
    public displayedColumns = ["productName", "description", "productTypeName", "validFromDate", "validToDate", "actions"];
    public dataSource: MatTableDataSource<IProduct[]>;
    public dataSubject = new BehaviorSubject<any[]>([]);
    public dataFilterValueChange: Observable<string> = new Observable<string>();

    @ViewChild("inputFilterValue") public inputFilterValue: ElementRef;
    @ViewChild(MatPaginator) public paginator: MatPaginator;
    @ViewChild(MatSort) public sort: MatSort;
    //#endregion

    constructor(
        @Inject(NOTIFICATION_SERVICE_TOKEN) public toastService: INotificationService,
        @Inject(PIM_DATA_SERVICE_TOKEN) public pimDataService: IPimDataService,
    ) {
        //
    }

    public ngOnInit() {
        this.dataFilterValueChange = Observable.fromEvent(this.inputFilterValue.nativeElement, "keyup");
        this.refresh();
    }

    /**
     * Set the paginator after the view init since this component will
     * be able to query its view for the initialized paginator.
     */
    public ngAfterViewInit() {
        this.setupTable();
    }

    public refresh(): void {
        const filters: ITuple<any> = { value: ["name"], text: this.dataFilterValueChange };
        this.dataSource = new MatTableDataSource(this.pimDataService.getProductsResource(), this.dataSubject, this.paginator, this.sort, filters);
    }

    private setupTable(): void {
        if (this.dataSource) {
            (this.dataSource as any).paginator = this.paginator;
            (this.dataSource as any).sort = this.sort;
        }
    }
}

In filters (this.filters) from component.ts, it's decided how many columns will be filtered in the input value.

I hope I can help you.

Best regards.

angular-automatic-lock-bot[bot] commented 5 years ago

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.