Skip to content

Commit 835074b

Browse files
committed
feat: getUserValues via gRPC
1 parent 379892c commit 835074b

12 files changed

+3424
-1819
lines changed

package-lock.json

Lines changed: 2166 additions & 1254 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/api/attrs/attrs.service.ts

Lines changed: 25 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {Injectable} from '@angular/core';
22
import {
33
AttrAttribute,
4-
AttrAttributeID,
54
AttrAttributesRequest,
65
AttrAttributeType,
76
AttrListOptionsRequest,
@@ -11,9 +10,9 @@ import {
1110
import {AttrsClient} from '@grpc/spec.pbsc';
1211
import {Empty} from '@ngx-grpc/well-known-types';
1312
import {APIPaginator, APIService} from '@services/api.service';
14-
import {APIItem} from '@services/item';
15-
import {Observable} from 'rxjs';
16-
import {map, shareReplay} from 'rxjs/operators';
13+
import {getAttrsTranslation} from '@utils/translations';
14+
import {Observable, of} from 'rxjs';
15+
import {map, shareReplay, switchMap} from 'rxjs/operators';
1716

1817
export interface APIAttrListOption {
1918
childs?: APIAttrListOption[];
@@ -46,34 +45,7 @@ export interface APIAttrConflictsGetResponse {
4645
paginator: APIPaginator;
4746
}
4847

49-
export interface APIAttrUserValuesOptions {
50-
fields?: string;
51-
item_id: number;
52-
limit?: number;
53-
page?: number;
54-
user_id?: number;
55-
zone_id?: number;
56-
}
57-
58-
export type APIAttrAttributeValue = number | string | string[];
59-
60-
export interface APIAttrUserValue {
61-
attribute_id: number;
62-
empty: boolean;
63-
item: APIItem | null;
64-
item_id: number;
65-
path: null | string[];
66-
unit: APIAttrUnit | null;
67-
update_date: null | string;
68-
user_id: string;
69-
value: APIAttrAttributeValue | null;
70-
value_text: null | string;
71-
}
72-
73-
export interface APIAttrUserValueGetResponse {
74-
items: APIAttrUserValue[];
75-
paginator: APIPaginator;
76-
}
48+
export type APIAttrAttributeValue = boolean | number | string | string[];
7749

7850
export interface APIAttrAttribute {
7951
childs?: APIAttrAttribute[];
@@ -112,6 +84,11 @@ function toTree(items: AttrAttribute[], parentID: string): AttrAttributeTreeItem
11284
providedIn: 'root',
11385
})
11486
export class APIAttrsService {
87+
private readonly attrs$ = this.attrsClient.getAttributes(new AttrAttributesRequest()).pipe(
88+
map((response) => response.items),
89+
shareReplay(1),
90+
);
91+
11592
public readonly attributeTypes$: Observable<AttrAttributeType[]> = this.attrsClient
11693
.getAttributeTypes(new Empty())
11794
.pipe(
@@ -142,40 +119,8 @@ export class APIAttrsService {
142119
);
143120
}
144121

145-
public getAttribute$(id: string): Observable<AttrAttribute> {
146-
return this.attrsClient.getAttribute(new AttrAttributeID({id}));
147-
}
148-
149-
public getUserValues$(options: APIAttrUserValuesOptions): Observable<APIAttrUserValueGetResponse> {
150-
const params: {[param: string]: string} = {};
151-
152-
if (options.fields) {
153-
params.fields = options.fields;
154-
}
155-
156-
if (options.item_id) {
157-
params.item_id = options.item_id.toString();
158-
}
159-
160-
if (options.zone_id) {
161-
params.zone_id = options.zone_id.toString();
162-
}
163-
164-
if (options.user_id) {
165-
params.user_id = options.user_id.toString();
166-
}
167-
168-
if (options.page) {
169-
params.page = options.page.toString();
170-
}
171-
172-
if (options.limit) {
173-
params.limit = options.limit.toString();
174-
}
175-
176-
return this.api.request<APIAttrUserValueGetResponse>('GET', 'attr/user-value', {
177-
params,
178-
});
122+
public getAttribute$(id: string): Observable<AttrAttribute | undefined> {
123+
return this.attrs$.pipe(map((attrs) => attrs?.find((attr) => attr.id === id)));
179124
}
180125

181126
public getAttributes$(zoneID: null | string, parentID: null | string): Observable<AttrAttributeTreeItem[]> {
@@ -209,4 +154,18 @@ export class APIAttrsService {
209154
public getListOptions$(attributeID: string | undefined): Observable<AttrListOptionsResponse> {
210155
return this.attrsClient.getListOptions(new AttrListOptionsRequest({attributeId: attributeID}));
211156
}
157+
158+
public getPath$(id: string): Observable<string[]> {
159+
return this.getAttribute$(id).pipe(
160+
switchMap((attr) => {
161+
if (!attr) {
162+
return of([]);
163+
}
164+
165+
return this.getPath$(attr.parentId).pipe(
166+
map((parentPath) => parentPath.concat(getAttrsTranslation(attr.name))),
167+
);
168+
}),
169+
);
170+
}
212171
}

src/app/cars/attrs-change-log/attrs-change-log.component.html

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -54,49 +54,48 @@ <h1 i18n id="header">History</h1>
5454
</tr>
5555
</thead>
5656
@if (items$ | async; as items) {
57-
@for (item of items.items; track item) {
57+
@for (row of items.items; track row.userValue) {
5858
<tr>
5959
<td>
60-
@if (item.update_date) {
61-
<p [textContent]="item.update_date | timeAgo" [ngbTooltip]="item.update_date | date: 'medium'"></p>
60+
@if (row.userValue.updateDate?.toDate(); as date) {
61+
<p [textContent]="date | timeAgo" [ngbTooltip]="date | date: 'medium'"></p>
6262
}
6363
</td>
6464
<td>
65-
@if (item.user$ | async; as user) {
65+
@if (row.user$ | async; as user) {
6666
<app-user [user]="user"></app-user>
6767
}
6868
</td>
6969
<td>
70-
@if (isModer$ | async) {
71-
<a [routerLink]="['/moder/items/item', item.item_id]" [innerHTML]="item.item.name_html"></a>
72-
} @else {
73-
<span [innerHTML]="item.item.name_html"></span>
70+
@if (row.item$ | async; as item) {
71+
@if (isModer$ | async) {
72+
<a [routerLink]="['/moder/items/item', row.userValue.itemId]" [innerHTML]="item.nameHtml"></a>
73+
} @else {
74+
<span [innerHTML]="item.nameHtml"></span>
75+
}
7476
}
7577
</td>
76-
<td class="attribute">
77-
@for (node of item.path; track node; let i = $index) {
78-
<span [textContent]="getAttrsTranslation(node)"></span>
79-
@if (!i) {
80-
<span> / </span>
78+
<td class="attribute text-center">
79+
@if (row.path$ | async; as path) {
80+
@for (node of path; track node; let last = $last) {
81+
<span>{{ node }}</span>
82+
@if (!last) {
83+
<span> / </span>
84+
}
8185
}
8286
}
8387
</td>
8488
<td style="text-align: center">
85-
{{ item.value_text }}
86-
@if (item.unit) {
87-
<span class="unit" [textContent]="getUnitAbbrTranslation(item.unit.id + '')"></span>
89+
{{ row.userValue.valueText }}
90+
@if (row.unitAbbr$ | async; as unitAbbr) {
91+
<span class="unit">{{ unitAbbr }}</span>
8892
}
8993
</td>
9094
<td class="link">
91-
<a [routerLink]="['/cars/car-specifications-editor/item_id', item.item_id]" i18n>to editor</a>
95+
<a [routerLink]="['/cars/car-specifications-editor/item_id', row.userValue.itemId]" i18n>to editor</a>
9296
</td>
9397
</tr>
9498
}
9599
}
96100
</table>
97101
</div>
98-
@if (items$ | async; as data) {
99-
<app-paginator [data]="data.paginator"></app-paginator>
100-
} @else {
101-
<div class="spinner-border" role="status"><span class="visually-hidden" i18n>Loading…</span></div>
102-
}

src/app/cars/attrs-change-log/attrs-change-log.component.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
}
99

1010
td.attribute {
11-
text-align: center;
12-
1311
span {
1412
white-space: nowrap;
1513
}

src/app/cars/attrs-change-log/attrs-change-log.component.ts

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,89 @@
1-
import {Component, OnDestroy, OnInit} from '@angular/core';
1+
import {Component, inject, OnDestroy, OnInit} from '@angular/core';
22
import {ActivatedRoute, Router} from '@angular/router';
3-
import {APIUser, APIUsersRequest} from '@grpc/spec.pb';
4-
import {UsersClient} from '@grpc/spec.pbsc';
3+
import {
4+
APIItem,
5+
APIUser,
6+
APIUsersRequest,
7+
AttrUserValue,
8+
AttrUserValuesFields,
9+
AttrUserValuesRequest,
10+
ItemFields,
11+
ItemRequest,
12+
} from '@grpc/spec.pb';
13+
import {AttrsClient, ItemsClient, UsersClient} from '@grpc/spec.pbsc';
514
import {NgbTypeaheadSelectItemEvent} from '@ng-bootstrap/ng-bootstrap';
615
import {ACLService, Privilege, Resource} from '@services/acl.service';
7-
import {APIPaginator} from '@services/api.service';
8-
import {APIItem} from '@services/item';
16+
import {LanguageService} from '@services/language';
917
import {PageEnvService} from '@services/page-env.service';
1018
import {UserService} from '@services/user';
11-
import {getAttrsTranslation, getUnitAbbrTranslation} from '@utils/translations';
19+
import {getUnitAbbrTranslation} from '@utils/translations';
1220
import {combineLatest, EMPTY, Observable, of, Subscription} from 'rxjs';
1321
import {catchError, debounceTime, distinctUntilChanged, map, shareReplay, switchMap} from 'rxjs/operators';
1422

15-
import {APIAttrAttributeValue, APIAttrsService, APIAttrUnit} from '../../api/attrs/attrs.service';
23+
import {APIAttrsService} from '../../api/attrs/attrs.service';
1624
import {ToastsService} from '../../toasts/toasts.service';
1725

26+
interface AttrUserValueListItem {
27+
item$: Observable<APIItem | null>;
28+
path$: Observable<string[]>;
29+
unitAbbr$: Observable<null | string | undefined>;
30+
user$: Observable<APIUser | null>;
31+
userValue: AttrUserValue;
32+
}
33+
1834
@Component({
1935
selector: 'app-cars-attrs-change-log',
2036
styleUrls: ['./attrs-change-log.component.scss'],
2137
templateUrl: './attrs-change-log.component.html',
2238
})
2339
export class CarsAttrsChangeLogComponent implements OnInit, OnDestroy {
40+
private readonly attrsClient = inject(AttrsClient);
41+
private readonly languageService = inject(LanguageService);
42+
private readonly itemsClient = inject(ItemsClient);
43+
private readonly attrsService = inject(APIAttrsService);
44+
45+
private readonly itemsCache = new Map<string, Observable<APIItem>>();
46+
2447
private querySub?: Subscription;
2548

2649
protected readonly isModer$ = this.acl.isAllowed$(Resource.GLOBAL, Privilege.MODERATE);
2750

28-
protected readonly userID$: Observable<number> = this.route.queryParamMap.pipe(
29-
map((params) => parseInt(params.get('user_id') || '', 10)),
30-
map((userID) => (userID ? userID : 0)),
51+
protected readonly userID$: Observable<string> = this.route.queryParamMap.pipe(
52+
map((params) => params.get('user_id') || ''),
3153
distinctUntilChanged(),
3254
debounceTime(10),
3355
);
3456

3557
protected readonly itemID$ = this.route.queryParamMap.pipe(
36-
map((params) => parseInt(params.get('item_id') || '', 10)),
37-
distinctUntilChanged(),
38-
debounceTime(10),
39-
);
40-
41-
protected readonly page$ = this.route.queryParamMap.pipe(
42-
map((params) => parseInt(params.get('page') || '', 10)),
58+
map((params) => params.get('item_id') || ''),
4359
distinctUntilChanged(),
4460
debounceTime(10),
4561
);
4662

4763
protected readonly items$: Observable<{
48-
items: {
49-
attribute_id: number;
50-
empty: boolean;
51-
item: APIItem | null;
52-
item_id: number;
53-
path: null | string[];
54-
unit: APIAttrUnit | null;
55-
update_date: null | string;
56-
user$: Observable<APIUser | null>;
57-
user_id: string;
58-
value: APIAttrAttributeValue | null;
59-
value_text: null | string;
60-
}[];
61-
paginator: APIPaginator;
62-
}> = combineLatest([this.userID$, this.itemID$, this.page$]).pipe(
63-
switchMap(([userID, itemID, page]) =>
64-
this.attrService.getUserValues$({
65-
fields: 'item.name_html,path,unit,value_text',
66-
item_id: itemID,
67-
page: page,
68-
user_id: userID ? userID : undefined,
69-
}),
64+
items: AttrUserValueListItem[];
65+
}> = combineLatest([this.userID$, this.itemID$]).pipe(
66+
switchMap(([userID, itemID]) =>
67+
this.attrsClient.getUserValues(
68+
new AttrUserValuesRequest({
69+
fields: new AttrUserValuesFields({valueText: true}),
70+
itemId: itemID,
71+
language: this.languageService.language,
72+
userId: userID ? userID : undefined,
73+
}),
74+
),
7075
),
7176
map((response) => ({
72-
items: response.items.map((item) => ({...item, user$: this.userService.getUser$(item.user_id)})),
73-
paginator: response.paginator,
77+
items: (response.items || []).map((userValue) => {
78+
const attr$ = this.attrsService.getAttribute$(userValue.attributeId);
79+
return {
80+
item$: this.getItem$(userValue.itemId),
81+
path$: this.attrsService.getPath$(userValue.attributeId),
82+
unitAbbr$: attr$.pipe(map((attr) => (attr?.unitId ? getUnitAbbrTranslation(attr.unitId) : null))),
83+
user$: this.userService.getUser$(userValue.userId),
84+
userValue,
85+
};
86+
}),
7487
})),
7588
shareReplay(1),
7689
);
@@ -114,7 +127,6 @@ export class CarsAttrsChangeLogComponent implements OnInit, OnDestroy {
114127
);
115128

116129
constructor(
117-
private readonly attrService: APIAttrsService,
118130
private readonly route: ActivatedRoute,
119131
private readonly router: Router,
120132
private readonly acl: ACLService,
@@ -124,6 +136,17 @@ export class CarsAttrsChangeLogComponent implements OnInit, OnDestroy {
124136
private readonly userService: UserService,
125137
) {}
126138

139+
private getItem$(id: string): Observable<APIItem | null> {
140+
let o$ = this.itemsCache.get(id);
141+
if (!o$) {
142+
o$ = this.itemsClient
143+
.item(new ItemRequest({fields: new ItemFields({nameHtml: true}), id: id}))
144+
.pipe(shareReplay(1));
145+
this.itemsCache.set(id, o$);
146+
}
147+
return o$;
148+
}
149+
127150
ngOnInit(): void {
128151
setTimeout(() => this.pageEnv.set({pageId: 103}), 0);
129152

@@ -162,10 +185,4 @@ export class CarsAttrsChangeLogComponent implements OnInit, OnDestroy {
162185
this.querySub.unsubscribe();
163186
}
164187
}
165-
166-
protected getAttrsTranslation(id: string): string {
167-
return getAttrsTranslation(id);
168-
}
169-
170-
protected readonly getUnitAbbrTranslation = getUnitAbbrTranslation;
171188
}

src/app/cars/cars.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {CommonModule} from '@angular/common';
22
import {NgModule} from '@angular/core';
3-
import {FormsModule} from '@angular/forms';
3+
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
44
import {NgbTooltipModule, NgbTypeaheadModule} from '@ng-bootstrap/ng-bootstrap';
55
import {UtilsModule} from '@utils/utils.module';
66

@@ -40,6 +40,7 @@ import {CarsSpecsAdminComponent} from './specs-admin/specs-admin.component';
4040
NgbTooltipModule,
4141
UtilsModule,
4242
ItemModule,
43+
ReactiveFormsModule,
4344
],
4445
})
4546
export class CarsModule {}

0 commit comments

Comments
 (0)