refactor(ccenter): migrate queues to pinia + ui-datalist [WTEL-10140] - #1429
Draft
dlohvinov wants to merge 17 commits into
Draft
refactor(ccenter): migrate queues to pinia + ui-datalist [WTEL-10140]#1429dlohvinov wants to merge 17 commits into
dlohvinov wants to merge 17 commits into
Conversation
…EL-10140](https://webitel.atlassian.net/browse/WTEL-10140) Groundwork for the datalist migration, with the page still on vuex. The ten per-type queue schemas and the queues api module both existed here and in api-services. The app now uses the api-services copies, so the two cannot drift while the migration is in flight: `queueStateMap` becomes `getQueueDefaults(type)`, and the four other modules that reached into `queues/api/queues` for `getLookup` import `QueuesAPI` from the package. Adds the pinia stores — datalist, card, permissions — which nothing renders yet, plus the pieces the card page will need: a `Queue` type widening `EngineQueue` with the `payload` shape the generated models omit, the per-type tab table lifted out of `opened-queue.vue`, and `useQueueTypeControls`, which replaces the identical `specificControls` computed in each of the three form tabs. Control ids are untouched, so the templates' `v-if`s carry over as-is. `QueueTypeProperties` becomes TypeScript; it was previously invisible to `vue-tsc`, since the app sets `allowJs: false`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…EL-10140](https://webitel.atlassian.net/browse/WTEL-10140) The registry now runs on `useQueuesDatalistStore`. Sorting, paging, column state and the filter set come from the store, and all of it persists to the URL and local storage the way every other migrated registry does. Search moves from a bare `wt-search-bar` to `dynamic-filter-search`, and the four filters become a `TableFiltersPanel`. `queueType` and `team` come from the shared filter set — the queue-type filter applies the same `VITE_STAGING_ENV` gate the app's own options list did, so that list goes away, as does the team lookup api. Tags stays app-side: `FilterOption.Tag` is the call-history tag enum, and its name is not the param `searchQueue` expects. `FilterConfig` keeps only the keys it knows, so a `searchRecords` passed through `createFilterConfig` would be dropped — the field component calls the api directly instead. The global state switcher moves into its own component. Its debounce works now: the page defined `debouncedFetchGlobalState()` as a method that reassigned itself to a debounced function on first call, so the first invocation only ever built the debouncer and never fetched. Two behaviour changes worth knowing. Filters now survive leaving and returning to the page, like every other migrated registry. And the WTEL-9657 workaround that preserved `?type=` on reset is gone with its cause: the old panel reset by rewriting the whole route query, on unmount, wiping the param the create-queue popup had just set. Also adds the two composables the card page needs next — the vuex bridge that keeps the un-migrated tabs fed, and `useEnsureQueueSaved`, which routes their "add before the queue exists" flow through a validated save instead of the mixin's unvalidated one. The five component specs are deleted rather than ported: all were `shallowMount` + `exists()` smoke tests bound to mixin mocks, and two overrode a `computed`, which `<script setup>` does not allow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-10140](https://webitel.atlassian.net/browse/WTEL-10140) The card page and its five form tabs now run on `useQueuesCardStore`. Tabs render through `router-view` rather than a local component registry, the permissions tab comes from the package, and every field binds with `v-model` against the draft instead of dispatching a mutation per keystroke — the `_dirty` bookkeeping that went with those goes away. `manualSetup` on `useCardComponent`, because a new queue's shape depends on `?type=`, which the card store knows nothing about. Owning `initialize` here lets `getQueueDefaults(type)` merge into the draft before anything reads it, which is what gives Regle a complete field set to build `$fields` from. The cost is re-implementing three small things: `initialize`, `$reset` on unmount, and the "replace `new` in the url once the server assigns an id" watcher — `useCardRouting` is not exported. The local watcher also keeps the route query, which `useCardTabs`' own `changeTab` drops; without that, `?type=` was lost on the first tab switch of an unsaved queue. Validation is no longer assembled per type in the component. The 250-line Vuelidate switch is gone; fields read `validationFields`, and the asterisk on a type-dependent field comes from the shared rule table rather than the old `:required="v.itemInstance.calendar"` trick, which was truthy only because that type's branch had declared a rule. The WTEL-8174 rule — first enabling prolongation also enables the timeout retry — moves from a vuex action to a watcher on the Processing tab. The WTEL-3268 default alert tone and the quirk where the `renewalSec` control is namespaced under `prolongationOptions` while its value sits at the top of `taskProcessing` are both preserved as they were. The six nested tabs still run on vuex and read their parent id from that store, so `useLegacyQueueVuexBridge` mirrors the pinia card into it. The mirror is established before `router-view` renders — a legacy tab mounting first would read id 0 and load the wrong list. Queue strategy, resource strategy and the two lookups move out of `store/`, which the sweep commit deletes; strategy and time-base-score now come from api-services rather than being declared twice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
…10140](https://webitel.atlassian.net/browse/WTEL-10140) First of the nested tabs, and the template the rest follow: a datalist store scoped under the queue's namespace, a popup driven by `useNestedCardComponent` off the `:hookId?` route param, and validation from the shared Zod schema. Persistence is off for this table. Route persistence writes un-namespaced `filters`/`page`/`size`/`sort` keys and every tab on the card shares one url, so leaving it on would have the tabs overwrite each other's state. Adding a hook to a queue that does not exist yet still works: the tab asks the card to save first, through the `ensureQueueSaved` it provides. That path validates, unlike the mixin's, which dispatched the parent's `ADD_ITEM` directly and could persist a half-filled queue. Also adds `useDeleteConfirmation`, a thin wrapper over ui-sdk's composable that re-creates its refs locally. The app and the symlinked sdk resolve different copies of vue, so refs crossing that boundary are not the app's `Ref` type and vue-tsc will not unwrap them in a template — every `:delete-count` binding reported an error. It is a local-linking artifact that CI would not see, but without the wrapper each migrated table repeats the same two errors, and the typecheck stops being a useful signal. Applying it to the queues table too brings the app back to its pre-existing error count, so this branch adds none. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…0140](https://webitel.atlassian.net/browse/WTEL-10140) Both follow the hooks template: a datalist store with persistence off, a popup on the existing `:bucketId?` / `:resourceId?` route param, and the shared Zod schema for validation. Two behaviours preserved rather than tidied. The buckets state column reads as "enabled" while the stored field is `disabled`, so the switcher stays inverted on both the binding and the patch. And neither tab asks for confirmation before deleting — unlike hooks — which is how they behave today; changing that is a UX decision, not part of a refactor. Resource groups has no `patch` on its service, so the table has no inline column to toggle and none is invented. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nto a computed [WTEL-10140](https://webitel.atlassian.net/browse/WTEL-10140) Same shape as the other nested tabs, plus the one piece of extra behaviour: the popup listing a skill's buckets. That popup was backed by store state and a `GET_ITEM_BUCKETS` action, but the action only searched `dataList` for the row it was given — every row already carries its own `buckets`. It is a computed now, and the shared `object-list-popup` renders it, so `bucketsPopupMixin`, the bespoke popup component and the store's `buckets` state all go away. Two fixes fall out of that. The lookup compares ids as strings, where the old code did `parseInt` against ids that can be strings; and closing the popup strips the query param instead of `$router.go(-1)`, which went somewhere else entirely when the url had been opened directly. Capacity keeps its existing arrangement: the column renders "min - max" while sorting on `max_capacity`, and the cross-field rule that keeps min below max now comes from the shared Zod schema rather than a pair of Vuelidate validators. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…L-10140](https://webitel.atlassian.net/browse/WTEL-10140) Membership is edited from the agent side, so this tab only lists. There is no card store and no popup — `createTableStore` needs `getList` alone. Its edit and delete row actions are gone rather than ported: the api module has no `delete`, and `editItem` pushed an `agentId` param no route declares, so neither did anything. The supervisors and skills popups keep their deep-linkable query params but read the collection straight off the row, which already carries it. That drops the store getter and the `dataList` watcher that re-derived it, and closing now strips the param instead of `$router.go(-1)`. `agentStatusMixin` becomes `useAgentStatusIndicator`. The mixin file stays — three other agent tables still use it. Search moves to `dynamic-filter-search`, so the tab picks up the same filter handling as every other migrated table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…https://webitel.atlassian.net/browse/WTEL-10140) Last of the nested tabs, and the only one that changes the card page. The card used to render this tab's filters in its own actions panel, picking the component out of a `currentTab.filters` string and handing it a vuex namespace — so the page had to know about one tab's internals, and no other tab could ever have filters. The panel now lives in the tab, behind its own filters icon, and the card's actions panel is gone. Filters become a `TableFiltersPanel`. `agent` comes from the shared set; the other three are app-side configs, since a queue log's result codes and its two ranges are not shared vocabulary. `joinedAt` and `duration` each collapse from two flat filters into one `{ from, to }` value, which is what makes `duration` work at all — the api read `durationFrom`/`durationTo` while the store only ever set `duration`, so those bounds were never sent. The log still opens on today, and reset returns to today rather than clearing the range: an unbounded attempt history is not a useful default. This tab keeps persistence, unlike its siblings. It is a report, a filtered view of it is worth sharing as a url, and no other tab writes those keys. With this the last nested table leaves vuex; only members and permissions are still registered as child modules. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…10140](https://webitel.atlassian.net/browse/WTEL-10140) Members is a second entity rather than a tab — its own page, its own card, CSV import, a reset action and an in-memory sub-collection — so it lands last. The card uses `useNestedCardComponent`: every members request needs the queue id, and only that composable threads a `parentId` through. It also has no `$reset` on unmount, because the route param it watches never clears while the page is open, so the card adds one — otherwise the next member opened would start on the previous one's draft. Communications stop being a store module. They have no endpoint of their own and save with the member, so they are plain array editing on the draft, in a composable that can be tested. The popup keeps its route param for deep-linking but is deliberately hand-rolled: `useNestedCardComponent` needs an entity with an id, and a communication is addressed by its index in an unsaved draft. It validates against the same Zod schema, which retires the bespoke dtmf validator. Deleting a communication now matches by identity. Matching on destination and type meant two identical rows deleted whichever came first. Bulk delete, reset and the affected-row count read `filtersManager` directly. "Delete selected" stays one request; `deleteEls` would fire one per row, which is right for the row icon and wrong here. `from`/`to` become one `createdAt` range, and `cause`/`priority` are renamed to `stopCause`/`memberPriority` to avoid the shared set's `HangupCause` and `CasePriority`. The api accepts both spellings. The mislabelled `settings` icon becomes a real column selector, which is what it was pretending to be now that the headers store is real. `import-csv-upload-action` moves across in the same commit — it imported both the members api and the csv mixin, and was the only thing outside this module reaching into it. With this the queue's last child module leaves vuex; only permissions remains. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…//webitel.atlassian.net/browse/WTEL-10140) Nothing reads it any more, so the module, its headers, the permissions child and the registration in the contact-center aggregator all go. With it goes `useLegacyQueueVuexBridge`, which existed only to keep the un-migrated tabs fed while the card ran on pinia, and the `inheritAttrs: false` lines that absorbed the `namespace` and vuelidate props the card used to pass every tab. `TypesResourceStrategy` is deleted rather than converted — general reads it from api-services now. `AmdAiLabels` becomes TypeScript; with `allowJs: false` it was invisible to the typechecker where it sat. The members dummy artwork goes too: empty states come from `useTableEmpty`. Sweep is clean — no `ccenter/queues` namespace left anywhere, no table or card mixin under the module, and no `.js` files. The seven shared mixins this branch stopped using all still have other consumers and are untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
https://webitel.atlassian.net/browse/WTEL-10140) The five component specs this branch deleted were `shallowMount` + `exists()` smoke tests bound to mixin mocks, and nothing replaced them. These cover the places where a mistake produces no error at all. - `useNormalizeCsvMembers` — the riskiest pure logic in the module and previously untested: zipping the parallel destination/code columns, stopping at the shorter one, skipping unknown codes, rejecting bad dtmf and rows left with no communications, and keying variables by their mapped csv tags. - `useMemberCommunications` — including the duplicate-row case, which is the only one that can tell identity-based deletion from the value-based matching it replaced. - `useQueueTypeControls` — the control map per type, dotted ids surviving intact, an unknown type yielding `{}` rather than throwing as the old computed did, and `isFieldRequired` agreeing with the shared rule table. - `queueTabs` — every tab id resolves to a declared route name. A typo there only shows when someone clicks the tab. - `headers` across all eight tables — `show: true` everywhere, since a header without it loses its column *and* stops its field being requested, plus the field names that deliberately disagree with their column: the queue's snake_case `resource_groups`, the bucket's inverted `disabled`, the log duration sorting on `joined_at`. - `filtersOptions` — filter names match the params each api reads. That invariant is invisible from either side alone, and breaking it is what left the queue log's duration filter doing nothing for as long as it existed. Writing them turned up a real bug: `useMemberCommunications.remove` compared raw objects against the draft's reactive proxies, so `Set.has` never matched. It happened to work from the tab, which passes proxies straight back, but any caller holding the underlying object would have deleted nothing. Both sides go through `toRaw` now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2 tasks
…L-10140](https://webitel.atlassian.net/browse/WTEL-10140) Co-authored-by: Cursor <cursoragent@cursor.com>
…/webitel.atlassian.net/browse/WTEL-10140) Co-authored-by: Cursor <cursoragent@cursor.com>
…://webitel.atlassian.net/browse/WTEL-10140) Drop custom stopCause/memberPriority widgets. Locales link to ui-datalist. Co-authored-by: Cursor <cursoragent@cursor.com>
…//webitel.atlassian.net/browse/WTEL-10140) Co-authored-by: Cursor <cursoragent@cursor.com>
…webitel.atlassian.net/browse/WTEL-10140) OfferingAt, attempts, name, destination. Filter badge shows when any panel filter is set. Co-authored-by: Cursor <cursoragent@cursor.com>
…40](https://webitel.atlassian.net/browse/WTEL-10140) Drop app-local widgets; panels now use ui-datalist FilterOptions whose names still match the request params. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Migrates the queues section — the registry, the card, its seven nested tabs and
the members sub-entity — from Vuex + mixins onto Pinia +
@webitel/ui-datalist,and deletes the Vuex module at the end.
165 files, −2,500 lines net. Eleven commits, each of which typechecks,
lints, tests and builds on its own; the legacy module is removed only in the
last one.
Important
Blocked on webitel/webitel-ui-sdk#1665. This branch imports
getQueueDefaults,queueSchema,queueTypeRules, seven api modules andQueuesAPI.getPermissionsList, none of which are in the published@webitel/api-servicesyet. CI will be red until that lands and publishes.Left as a draft for that reason.
Shape
Worth knowing when reviewing
Type-dependent queues. A new queue's fields depend on
?type=, whichcreateCardStoreknows nothing about, so the card usesmanualSetupand seedsgetQueueDefaults(type)into the draft before anything reads it. That is whatgives Regle a complete key set to build
$fieldsfrom — it derives them fromstate, not from the schema, so a missing key costs a field its required marker
and error text with no error anywhere. The 250-line per-type Vuelidate switch
is gone; the asterisk on a type-dependent field now comes from the shared rule
table rather than the old
:required="v.itemInstance.calendar"trick.Create-on-demand is kept. Adding a hook or skill to an unsaved queue still
saves the queue first, but through the card's own validated
save, provided tothe tabs. The mixin dispatched
ADD_ITEMdirectly and could persist ahalf-filled queue; now an invalid one blocks the add and shows its errors.
The logs filters move into the logs tab. The card used to render them in
its actions panel via a
currentTab.filterscomponent name, so the page had toknow one tab's internals and no other tab could ever have filters.
Persistence is off for the five in-card tabs. Route persistence writes
un-namespaced
filters/page/size/sort, and the tabs share one url. Logsand members keep it — they have their own routes, and a filtered log is worth
sharing.
Behaviour changes
registry. The WTEL-9657
?type=-preserving reset goes with its cause: theold panel reset by rewriting the whole route query, on unmount.
delete, andeditItempushed a param no route declares, so neither worked.settingsicon becomes a real column selector.durationfilter starts working. The store helddurationas
{from, to}while the api readdurationFrom/durationTo, so the boundswere never sent.
still asks. Preserved as-is — unifying them is a UX call, not a refactor.
is already true of every migrated entity.
Tests
The five component specs here were
shallowMount+exists()smoke testsbound to mixin mocks, two of them overriding a
computed, which<script setup>does not allow. They are replaced by 86 tests over the logic that canfail silently: the CSV normaliser, the member communications collection, the
per-type control map, tab-to-route mapping, table headers across all eight
tables, and filter names matching the params each api reads.
Writing them found a real bug in this branch:
useMemberCommunications.removecompared raw objects against the draft's reactive proxies, so
Set.hasnevermatched. It worked from the tab, which passes proxies back, but any caller
holding the underlying object would have deleted nothing.
Verification
typecheck at the repo's pre-existing error count (this branch adds none) · lint
clean · 136 unit tests · production build succeeds.
Not verified: the app was never exercised against a backend. The per-type
$fieldsseeding is the one thing no automated check covers — please open aqueue of each of the ten types, create and edit, and confirm required markers
and error text appear on Params and Processing.
Related Tasks
Related PR's / Issues