Passed
Push — trunk ( d9aed8...6cdd75 )
by Christian
12:28 queued 13s
created

src/Administration/Resources/app/administration/src/app/mixin/listing.mixin.ts   C

Complexity

Total Complexity 53
Complexity/F 1.89

Size

Lines of Code 383
Function Count 28

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 295
dl 0
loc 383
rs 6.96
c 0
b 0
f 0
wmc 53
mnd 25
bc 25
fnc 28
bpm 0.8928
cpm 1.8928
noi 0

27 Functions

Rating   Name   Duplication   Size   Complexity  
A listing.mixin.ts ➔ term 0 4 2
A listing.mixin.ts ➔ getList 0 5 1
B listing.mixin.ts ➔ created 0 29 6
A listing.mixin.ts ➔ onSearch 0 13 2
A listing.mixin.ts ➔ addQueryScores 0 17 3
A listing.mixin.ts ➔ sortBy 0 3 1
A listing.mixin.ts ➔ selection 0 3 5
A listing.mixin.ts ➔ selectionCount 0 3 1
A listing.mixin.ts ➔ maxPage 0 2 1
A listing.mixin.ts ➔ searchRankingFields 0 7 2
A listing.mixin.ts ➔ filters 0 6 1
A listing.mixin.ts ➔ onPageChange 0 13 2
A listing.mixin.ts ➔ onRefresh 0 3 1
A listing.mixin.ts ➔ sortDirection 0 3 1
A listing.mixin.ts ➔ selectionArray 0 3 1
A listing.mixin.ts ➔ routeName 0 3 1
A listing.mixin.ts ➔ onSwitchFilter 0 5 1
B listing.mixin.ts ➔ onSortColumn 0 26 6
A listing.mixin.ts ➔ getMainListingParams 0 22 2
A listing.mixin.ts ➔ updateRoute 0 33 2
A listing.mixin.ts ➔ resetListing 0 12 1
A listing.mixin.ts ➔ onSort 0 17 2
A listing.mixin.ts ➔ isValidTerm 0 3 1
A listing.mixin.ts ➔ data 0 30 1
A listing.mixin.ts ➔ updateSelection 0 3 1
A listing.mixin.ts ➔ updateData 0 14 3
A listing.mixin.ts ➔ currentSortBy 0 3 2

How to fix   Complexity   

Complexity

Complex classes like src/Administration/Resources/app/administration/src/app/mixin/listing.mixin.ts often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/**
2
 * @package admin
3
 */
4
5
/* @private */
6
import type { Dictionary } from 'vue-router/types/router';
7
import type { RawLocation } from 'vue-router';
8
import type Criteria from '@shopware-ag/admin-extension-sdk/es/data/Criteria';
9
import { defineComponent } from 'vue';
10
11
/* @private */
12
export {};
13
14
/* Mixin uses many untyped dependencies */
15
/* eslint-disable @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,max-len,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any */
16
17
/**
18
 * @deprecated tag:v6.6.0 - Will be private
19
 */
20
export default Shopware.Mixin.register('listing', defineComponent({
21
    inject: ['searchRankingService', 'feature'],
22
23
    data(): {
24
        page: number,
25
        limit: number,
26
        total: number,
27
        sortBy: string|null,
28
        sortDirection: string,
29
        naturalSorting: boolean,
30
        selection: Record<string, any>,
31
        term: string|undefined,
32
        disableRouteParams: boolean,
33
        searchConfigEntity: string|null,
34
        entitySearchable: boolean,
35
        freshSearchTerm: boolean,
36
        previousRouteName: string,
37
        } {
38
        return {
39
            page: 1,
40
            limit: 25,
41
            total: 0,
42
            sortBy: null,
43
            sortDirection: 'ASC',
44
            naturalSorting: false,
45
            selection: [],
46
            term: undefined,
47
            disableRouteParams: false,
48
            searchConfigEntity: null,
49
            entitySearchable: true,
50
            freshSearchTerm: false,
51
            previousRouteName: '',
52
        };
53
    },
54
55
    computed: {
56
        maxPage() {
57
            return Math.ceil(this.total / this.limit);
58
        },
59
60
        routeName() {
61
            return this.$route.name;
62
        },
63
64
        selectionArray() {
65
            return Object.values(this.selection);
66
        },
67
68
        selectionCount() {
69
            return this.selectionArray.length;
70
        },
71
72
        filters(): {
73
            active: boolean,
74
        }[] {
75
            // You can create your custom filters by defining the computed property "filters"
76
            return [];
77
        },
78
79
        searchRankingFields() {
80
            if (!this.searchConfigEntity) {
81
                return {};
82
            }
83
84
            return this.searchRankingService.getSearchFieldsByEntity(this.searchConfigEntity);
85
        },
86
87
        currentSortBy() {
88
            return this.freshSearchTerm ? null : this.sortBy;
89
        },
90
    },
91
92
    created() {
93
        if (this.feature.isActive('VUE3')) {
94
            this.previousRouteName = this.$route.name as string;
95
        }
96
97
        if (this.disableRouteParams) {
98
            this.getList();
99
            return;
100
        }
101
102
        const actualQueryParameters: Dictionary<(string|null)[]|string|boolean> = this.$route.query;
103
104
        // When no route information are provided
105
        if (Shopware.Utils.types.isEmpty(actualQueryParameters)) {
106
            this.resetListing();
107
        } else {
108
            // When we get the parameters on the route, true and false will be a string so we should convert to boolean
109
            Object.keys(actualQueryParameters).forEach((key) => {
110
                if (actualQueryParameters[key] === 'true') {
111
                    actualQueryParameters[key] = true;
112
                } else if (actualQueryParameters[key] === 'false') {
113
                    actualQueryParameters[key] = false;
114
                }
115
            });
116
117
            // otherwise update local data and fetch from server
118
            this.updateData(actualQueryParameters);
119
            this.getList();
120
        }
121
    },
122
123
    watch: {
124
        // Watch for changes in query parameters and update listing
125
        '$route'(newRoute, oldRoute) {
126
            if (this.disableRouteParams || (this.feature.isActive('VUE3') && this.previousRouteName !== newRoute.name)) {
127
                return;
128
            }
129
130
            const query = this.$route.query;
131
132
            if (Shopware.Utils.types.isEmpty(query)) {
133
                this.resetListing();
134
            }
135
136
            // Update data information from the url
137
            this.updateData(query);
138
139
            // @ts-expect-error - properties are defined in base component
140
            if (newRoute.query[this.storeKey] !== oldRoute.query[this.storeKey] && this.filterCriteria.length) {
141
                // @ts-expect-error - filterCriteria is defined in base component
142
                this.filterCriteria = [];
143
                return;
144
            }
145
146
            // Fetch new list
147
            this.getList();
148
        },
149
150
        selection() {
151
            Shopware.State.commit('shopwareApps/setSelectedIds', Object.keys(this.selection));
152
        },
153
154
        term(newValue) {
155
            if (newValue && newValue.length) {
156
                this.freshSearchTerm = true;
157
            }
158
        },
159
160
        sortBy() {
161
            this.freshSearchTerm = false;
162
        },
163
164
        sortDirection() {
165
            this.freshSearchTerm = false;
166
        },
167
    },
168
169
    methods: {
170
        updateData(customData: {
171
            page?: number,
172
            limit?: number,
173
            term?: string,
174
            sortBy?: string,
175
            sortDirection?: string,
176
            naturalSorting?: boolean,
177
        }) {
178
            this.page = parseInt(customData.page as unknown as string, 10) || this.page;
179
            this.limit = parseInt(customData.limit as unknown as string, 10) || this.limit;
180
            this.term = customData.term ?? this.term;
181
            this.sortBy = customData.sortBy || this.sortBy;
182
            this.sortDirection = customData.sortDirection || this.sortDirection;
183
            this.naturalSorting = customData.naturalSorting || this.naturalSorting;
184
        },
185
186
        updateRoute(customQuery: {
187
            limit?: number,
188
            page?: number,
189
            term?: string,
190
            sortBy?: string,
191
            sortDirection?: string,
192
            naturalSorting?: boolean,
193
        }, queryExtension = {}) {
194
            // Get actual query parameter
195
            const query = customQuery || this.$route.query;
196
            const routeQuery = this.$route.query;
197
198
            // Create new route
199
            const route = {
200
                name: this.$route.name,
201
                params: this.$route.params,
202
                query: {
203
                    limit: query.limit || this.limit,
204
                    page: query.page || this.page,
205
                    term: query.term || this.term,
206
                    sortBy: query.sortBy || this.sortBy,
207
                    sortDirection: query.sortDirection || this.sortDirection,
208
                    naturalSorting: query.naturalSorting || this.naturalSorting,
209
                    ...queryExtension,
210
                },
211
            };
212
213
            // If query is empty then replace route, otherwise push
214
            if (Shopware.Utils.types.isEmpty(routeQuery)) {
215
                void this.$router.replace(route as unknown as RawLocation);
216
            } else {
217
                void this.$router.push(route as unknown as RawLocation);
218
            }
219
        },
220
221
        resetListing() {
222
            this.updateRoute({
223
                // @ts-expect-error
224
                name: this.$route.name,
225
                query: {
226
                    limit: this.limit,
227
                    page: this.page,
228
                    term: this.term,
229
                    sortBy: this.sortBy,
230
                    sortDirection: this.sortDirection,
231
                    naturalSorting: this.naturalSorting,
232
                },
233
            });
234
        },
235
236
        getMainListingParams() {
237
            if (this.disableRouteParams) {
238
                return {
239
                    limit: this.limit,
240
                    page: this.page,
241
                    term: this.term,
242
                    sortBy: this.sortBy,
243
                    sortDirection: this.sortDirection,
244
                    naturalSorting: this.naturalSorting,
245
                };
246
            }
247
            // Get actual query parameter
248
            const query = this.$route.query;
249
250
            return {
251
                limit: query.limit,
252
                page: query.page,
253
                term: query.term,
254
                sortBy: query.sortBy || this.sortBy,
255
                sortDirection: query.sortDirection || this.sortDirection,
256
                naturalSorting: query.naturalSorting || this.naturalSorting,
257
            };
258
        },
259
260
        updateSelection(selection: Record<string, any>) {
261
            this.selection = selection;
262
        },
263
264
        onPageChange(opts: {
265
            page: number,
266
            limit: number,
267
        }) {
268
            this.page = opts.page;
269
            this.limit = opts.limit;
270
            if (this.disableRouteParams) {
271
                this.getList();
272
                return;
273
            }
274
            this.updateRoute({
275
                page: this.page,
276
            });
277
        },
278
279
        onSearch(value: string|undefined) {
280
            this.term = value;
281
282
            if (this.disableRouteParams) {
283
                this.page = 1;
284
                this.getList();
285
                return;
286
            }
287
288
            this.updateRoute({
289
                term: this.term,
290
                page: 1,
291
            });
292
        },
293
294
        onSwitchFilter(filter: any, filterIndex: number) {
295
            this.filters[filterIndex].active = !this.filters[filterIndex].active;
296
297
            this.page = 1;
298
        },
299
300
        onSort({ sortBy, sortDirection }: {
301
            sortBy: string,
302
            sortDirection: string,
303
        }) {
304
            if (this.disableRouteParams) {
305
                this.updateData({
306
                    sortBy,
307
                    sortDirection,
308
                });
309
            } else {
310
                this.updateRoute({
311
                    sortBy,
312
                    sortDirection,
313
                });
314
            }
315
316
            this.getList();
317
        },
318
319
        onSortColumn(column: {
320
            dataIndex: string,
321
            naturalSorting: boolean,
322
        }) {
323
            if (this.disableRouteParams) {
324
                if (this.sortBy === column.dataIndex) {
325
                    this.sortDirection = (this.sortDirection === 'ASC' ? 'DESC' : 'ASC');
326
                } else {
327
                    this.sortDirection = 'ASC';
328
                    this.sortBy = column.dataIndex;
329
                }
330
                this.getList();
331
                return;
332
            }
333
334
            if (this.sortBy === column.dataIndex) {
335
                this.updateRoute({
336
                    sortDirection: (this.sortDirection === 'ASC' ? 'DESC' : 'ASC'),
337
                });
338
            } else {
339
                this.naturalSorting = column.naturalSorting;
340
                this.updateRoute({
341
                    sortBy: column.dataIndex,
342
                    sortDirection: 'ASC',
343
                    naturalSorting: column.naturalSorting,
344
                });
345
            }
346
        },
347
348
        onRefresh() {
349
            this.getList();
350
        },
351
352
        getList() {
353
            Shopware.Utils.debug.warn(
354
                'Listing Mixin',
355
                'When using the listing mixin you have to implement your custom "getList()" method.',
356
            );
357
        },
358
359
        isValidTerm(term: string) {
360
            return term && term.trim().length > 1;
361
        },
362
363
        async addQueryScores(term: string, originalCriteria: Criteria) {
364
            this.entitySearchable = true;
365
            if (!this.searchConfigEntity || !this.isValidTerm(term)) {
366
                return originalCriteria;
367
            }
368
            const searchRankingFields = await this.searchRankingService.getSearchFieldsByEntity(this.searchConfigEntity);
369
            // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
370
            if (!searchRankingFields || Object.keys(searchRankingFields).length < 1) {
371
                this.entitySearchable = false;
372
                return originalCriteria;
373
            }
374
375
            return this.searchRankingService.buildSearchQueriesForEntity(
376
                searchRankingFields,
377
                term,
378
                originalCriteria,
379
            );
380
        },
381
    },
382
}));
383