@webitel/ui-sdk 3.2.132 → 3.2.134

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webitel/ui-sdk",
3
- "version": "3.2.132",
3
+ "version": "3.2.134",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve-lib": "vue-cli-service serve",
@@ -142,10 +142,10 @@ export default {
142
142
 
143
143
  <style lang="scss">
144
144
  @import './variables.scss';
145
- @import './multiselect.scss';
146
145
  </style>
147
146
 
148
147
  <style lang="scss" scoped>
148
+ @import './multiselect.scss';
149
149
 
150
150
  .wt-select {
151
151
  width: 100%;
@@ -99,36 +99,35 @@ export default {
99
99
  </style>
100
100
 
101
101
  <style lang="scss" scoped>
102
-
103
- .wt-status-select.wt-select ::v-deep {
102
+ .wt-status-select.wt-select {
104
103
  box-shadow: var(--elevation-5);
105
104
 
106
- .multiselect {
105
+ :deep(.multiselect) {
107
106
  min-height: 0;
108
- }
109
107
 
110
- .multiselect__tags {
111
- min-height: 0;
112
- padding: var(--status-select-padding);
113
- border: none;
114
- }
108
+ .multiselect__tags {
109
+ min-height: 0;
110
+ padding: var(--status-select-padding);
111
+ border: none;
112
+ }
115
113
 
116
- .multiselect__select {
117
- top: 0;
118
- right: 0;
119
- }
114
+ .multiselect__select {
115
+ top: 0;
116
+ right: 0;
117
+ }
120
118
 
121
- .multiselect__content-wrapper {
122
- border: none;
119
+ .multiselect__content-wrapper {
120
+ border: none;
123
121
 
124
- .multiselect__option {
125
- min-height: 0;
126
- padding: 0;
127
- color: var(--status-select-option-color);
128
- }
122
+ .multiselect__option {
123
+ min-height: 0;
124
+ padding: 0;
125
+ color: var(--status-select-option-color);
126
+ }
129
127
 
130
- .multiselect__option--highlight {
131
- background: var(--status-select-bg--hover-color);
128
+ .multiselect__option--highlight {
129
+ background: var(--status-select-bg--hover-color);
130
+ }
132
131
  }
133
132
  }
134
133
  }
@@ -157,10 +157,11 @@ export default {
157
157
 
158
158
  <style lang="scss">
159
159
  @import './variables.scss';
160
- @import '../wt-select/multiselect.scss';
161
160
  </style>
162
161
 
163
162
  <style lang="scss" scoped>
163
+ @import '../wt-select/multiselect.scss';
164
+
164
165
  .wt-tags-input {
165
166
  width: 100%;
166
167
  }
package/src/css/main.scss CHANGED
@@ -1,7 +1,6 @@
1
1
  @import 'styleguide/styleguide';
2
2
  @import 'styleguide/display-helpers/display-helpers';
3
3
 
4
-
5
4
  @include wt-placeholder;
6
5
 
7
6
  :root {
@@ -0,0 +1,21 @@
1
+ import installOptionsRepository from '../../../../_install/InstallOptionsRepository';
2
+ import AgentStatusAPI from '../agent-status';
3
+
4
+ /* mock SDK method api response with instance mock
5
+ jest.spyOn(instance) used instead of jest.mock('@/app/api/instance) because WebStorm
6
+ doesn't watch path changes in jest.mock()
7
+ */
8
+ const patchMock = jest.fn();
9
+ jest.spyOn(installOptionsRepository.get('instance'), 'request')
10
+ .mockImplementation(patchMock);
11
+
12
+ describe('Agent Status API', () => {
13
+ it('patch: correctly processes response', async () => {
14
+ const payload = { agentId: 1, status: 'jest', pauseCause: 'jest' };
15
+ const expectedData = { id: 1, status: 'jest', payload: 'jest' };
16
+ await AgentStatusAPI.patch(payload);
17
+ expect(patchMock).toHaveBeenCalled();
18
+ const reqData = JSON.parse(patchMock.mock.calls[0][0].data);
19
+ expect(reqData).toEqual(expectedData);
20
+ });
21
+ });
@@ -0,0 +1,29 @@
1
+ import installOptionsRepository from '../../../../_install/InstallOptionsRepository';
2
+ import PauseCauseAPI from '../pause-cause';
3
+
4
+ const items = [{ id: 1, name: 'jest' }];
5
+ const expectedResponse = {
6
+ next: false,
7
+ items: [{
8
+ id: 1,
9
+ name: 'jest',
10
+ durationMin: 0,
11
+ limitMin: 0,
12
+ }],
13
+ };
14
+
15
+ /* mock SDK method api response with instance mock
16
+ jest.spyOn(instance) used instead of jest.mock('@/app/api/instance) because WebStorm
17
+ doesn't watch path changes in jest.mock()
18
+ */
19
+ const getMock = jest.fn(() => ({ items }));
20
+ jest.spyOn(installOptionsRepository.get('instance'), 'request')
21
+ .mockImplementation(getMock);
22
+
23
+ describe('Agent Pause Cause API', () => {
24
+ it('getList: correctly processes response', async () => {
25
+ const response = await PauseCauseAPI.getList({ agentId: 0 });
26
+ expect(getMock).toHaveBeenCalled();
27
+ expect(response).toEqual(expectedResponse);
28
+ });
29
+ });
@@ -0,0 +1,28 @@
1
+ import { AgentServiceApiFactory } from 'webitel-sdk';
2
+ import installOptionsRepository from '../../../_install/InstallOptionsRepository';
3
+
4
+ let apiService;
5
+
6
+ const createApiService = () => apiService = new AgentServiceApiFactory(
7
+ installOptionsRepository.get('openAPIConfig'),
8
+ '',
9
+ installOptionsRepository.get('instance'),
10
+ );
11
+
12
+ export const patchAgentStatus = async ({ agentId, status, pauseCause }) => {
13
+ if (!apiService) createApiService();
14
+ try {
15
+ const res = await apiService.updateAgentStatus(agentId, {
16
+ status,
17
+ id: agentId,
18
+ payload: pauseCause,
19
+ }, undefined);
20
+ return { success: !!res };
21
+ } catch (err) {
22
+ throw err;
23
+ }
24
+ };
25
+
26
+ export default {
27
+ patch: patchAgentStatus,
28
+ };
@@ -0,0 +1,40 @@
1
+ import { AgentServiceApiFactory } from 'webitel-sdk';
2
+ import { SdkListGetterApiConsumer } from 'webitel-sdk/esm2015/api-consumers';
3
+ import installOptionsRepository from '../../../_install/InstallOptionsRepository';
4
+
5
+ const _getAgentPauseCauses = (getList) => function ({
6
+ agentId,
7
+ } = {}) {
8
+ const allowChange = true;
9
+ const params = [agentId, allowChange];
10
+ return getList(params);
11
+ };
12
+
13
+ const defaultListObject = {
14
+ name: '',
15
+ durationMin: 0,
16
+ limitMin: 0,
17
+ };
18
+
19
+ let apiService;
20
+ let listGetter;
21
+
22
+ const createApiService = () => {
23
+ apiService = new AgentServiceApiFactory(
24
+ installOptionsRepository.get('openAPIConfig'),
25
+ '',
26
+ installOptionsRepository.get('instance'),
27
+ );
28
+ listGetter = new SdkListGetterApiConsumer(apiService.searchPauseCauseForAgent,
29
+ { defaultListObject })
30
+ .setGetListMethod(_getAgentPauseCauses);
31
+ }
32
+
33
+ export const getAgentPauseCauses = (params) => {
34
+ if (!apiService) createApiService();
35
+ return listGetter.getList(params);
36
+ }
37
+
38
+ export default {
39
+ getList: getAgentPauseCauses,
40
+ };
@@ -0,0 +1,93 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import AgentStatus from '@webitel/ui-sdk/src/enums/AgentStatus/AgentStatus.enum';
3
+ import { createStore } from 'vuex';
4
+ import AgentStatusAPI from '../../api/agent-status';
5
+ import PauseCauseAPI from '../../api/pause-cause';
6
+ import statusSelect from '../../store/agent-status-select';
7
+ import WtCcAgentStatusSelect from '../wt-cc-agent-status-select.vue';
8
+
9
+ const pauseCauses = [{ name: 'jest1' }, { name: 'jest2' }];
10
+ const getAgentPauseCausesMock = jest.fn(() => ({ items: pauseCauses }));
11
+ jest.spyOn(PauseCauseAPI, 'getList').mockImplementation(getAgentPauseCausesMock);
12
+
13
+ const agentStatusMock = jest.fn(() => {
14
+ });
15
+ jest.spyOn(AgentStatusAPI, 'patch').mockImplementation(agentStatusMock);
16
+
17
+ const namespace = 'agents';
18
+ const agent = {
19
+ status: '',
20
+ agentId: 1,
21
+ };
22
+ const updateAgentStatusMock = jest.fn();
23
+ jest.spyOn(statusSelect.actions, 'UPDATE_AGENT_STATUS')
24
+ .mockImplementation(updateAgentStatusMock);
25
+
26
+ const store = createStore({
27
+ modules: {
28
+ [namespace]: {
29
+ state: { agent },
30
+ modules: { statusSelect },
31
+ namespaced: true,
32
+ },
33
+ },
34
+ });
35
+
36
+ const mountOptions = {
37
+ global: { plugins: [store] },
38
+ props: {
39
+ agentId: agent.agentId,
40
+ status: agent.status,
41
+ },
42
+ };
43
+
44
+ describe('Wt Cc Agent Status Select', () => {
45
+ beforeEach(() => {
46
+ updateAgentStatusMock.mockClear();
47
+ getAgentPauseCausesMock.mockClear();
48
+ });
49
+ it('renders a component', () => {
50
+ const wrapper = shallowMount(WtCcAgentStatusSelect, mountOptions);
51
+ expect(wrapper.exists())
52
+ .toBe(true);
53
+ });
54
+ it(`at wt-status-select "change" to "online" event, triggers UPDATE_AGENT_STATUS
55
+ with "online" status`, () => {
56
+ const wrapper = shallowMount(WtCcAgentStatusSelect, mountOptions);
57
+ wrapper.findComponent({ name: 'wt-status-select' })
58
+ .vm.$emit('change', AgentStatus.ONLINE);
59
+ const reqPayload = {
60
+ status: AgentStatus.ONLINE, agentId: agent.agentId, payload: undefined,
61
+ };
62
+ expect(agentStatusMock).toHaveBeenCalledWith(reqPayload);
63
+ });
64
+ it('at wt-status-select "change" to "pause" event, pause causes are loaded', async () => {
65
+ const wrapper = shallowMount(WtCcAgentStatusSelect, mountOptions);
66
+ wrapper.findComponent({ name: 'wt-status-select' })
67
+ .vm.$emit('change', AgentStatus.PAUSE);
68
+ await wrapper.vm.$nextTick();
69
+ expect(getAgentPauseCausesMock).toHaveBeenCalled();
70
+ });
71
+ it(`at wt-status-select "change" to "pause" event and pause causes truthy response,
72
+ pause-cause-popup appears`, async () => {
73
+ const wrapper = shallowMount(WtCcAgentStatusSelect, mountOptions);
74
+ wrapper.findComponent({ name: 'wt-status-select' })
75
+ .vm.$emit('change', AgentStatus.PAUSE);
76
+ await wrapper.vm.$nextTick(); // load pause causes
77
+ await wrapper.vm.$nextTick(); // render popup
78
+ expect(wrapper.findComponent({ name: 'wt-cc-pause-cause-popup' }).isVisible()).toBe(true);
79
+ });
80
+ it(`at pause-cause-popup "change" event, triggers UPDATE_AGENT_STATUS
81
+ with "pause" status and passed pauseCause`, async () => {
82
+ const pauseCause = 'jest';
83
+ const wrapper = shallowMount(WtCcAgentStatusSelect, mountOptions);
84
+ wrapper.vm.openPauseCausePopup();
85
+ await wrapper.vm.$nextTick();
86
+ wrapper.findComponent({ name: 'wt-cc-pause-cause-popup' })
87
+ .vm.$emit('change', pauseCause);
88
+ const reqPayload = {
89
+ status: AgentStatus.PAUSE, agentId: agent.agentId, pauseCause,
90
+ };
91
+ expect(agentStatusMock).toHaveBeenCalledWith(reqPayload);
92
+ });
93
+ });
@@ -0,0 +1,32 @@
1
+ import { shallowMount, mount } from '@vue/test-utils';
2
+ import WtCcPauseCausePopup from '../_internals/wt-cc-pause-cause-popup.vue';
3
+
4
+ describe('Pause cause popup', () => {
5
+ it('renders a component', () => {
6
+ const wrapper = shallowMount(WtCcPauseCausePopup);
7
+ expect(wrapper.exists())
8
+ .toBe(true);
9
+ });
10
+ it('at option input event, sets "selected" its value', () => {
11
+ const radioValue = 'jest';
12
+ const wrapper = mount(WtCcPauseCausePopup, {
13
+ props: { options: [radioValue] },
14
+ });
15
+ wrapper.findComponent({ name: 'wt-radio' }).vm.$emit('input');
16
+ expect(wrapper.vm.selected).toEqual(radioValue);
17
+ });
18
+ it('isDurationOverflow with limit = 0 returns false', () => {
19
+ const wrapper = shallowMount(WtCcPauseCausePopup);
20
+ const option = { durationMin: 10, limitMin: 0 };
21
+ expect(wrapper.vm.isDurationOverflow(option)).toBe(false);
22
+ });
23
+ it('at "ok" btn click, emits "change" with "selected" value', () => {
24
+ const selected = { name: 'jest' };
25
+ const expectedValue = selected.name;
26
+ const wrapper = shallowMount(WtCcPauseCausePopup, {
27
+ data: () => ({ selected }),
28
+ });
29
+ wrapper.findComponent({ name: 'wt-button' }).vm.$emit('click');
30
+ expect(wrapper.emitted().change[0]).toEqual([expectedValue]);
31
+ });
32
+ });
@@ -0,0 +1,124 @@
1
+ <template>
2
+ <wt-popup
3
+ class="wt-cc-pause-cause-popup"
4
+ min-width="480"
5
+ @close="close"
6
+ >
7
+ <template v-slot:title>
8
+ {{ $t('webitelUI.agentStatusSelect.pauseCausePopup.title') }}
9
+ </template>
10
+ <template v-slot:main>
11
+ <form @submit.prevent="setPause">
12
+ <ul>
13
+ <li
14
+ class="wt-cc-pause-cause-popup__option"
15
+ v-for="(option) of pauseCause"
16
+ :key="option.id"
17
+ >
18
+ <wt-radio
19
+ class="wt-cc-pause-cause-popup__option__radio"
20
+ :selected="selected.id"
21
+ :value="option.id"
22
+ :label="option.name"
23
+ @input="select(option)"
24
+ ></wt-radio>
25
+ <div class="wt-cc-pause-cause-popup__option__limits-wrapper">
26
+ <span
27
+ :class="{
28
+ 'wt-cc-pause-cause-popup__option__duration--overflow': option.isOverflow,
29
+ }"
30
+ >
31
+ {{ option.duration }}
32
+ </span>
33
+ /
34
+ <span>{{ option.limit }}</span>
35
+ </div>
36
+ </li>
37
+ </ul>
38
+ </form>
39
+ </template>
40
+ <template v-slot:actions>
41
+ <wt-button
42
+ :disabled="!selected"
43
+ @click="setPause"
44
+ >{{ $t('reusable.ok') }}
45
+ </wt-button>
46
+ <wt-button
47
+ color="secondary"
48
+ @click="close"
49
+ >{{ $t('reusable.cancel') }}
50
+ </wt-button>
51
+ </template>
52
+ </wt-popup>
53
+ </template>
54
+
55
+ <script setup>
56
+ import { computed, ref, toRef } from 'vue';
57
+ import { useI18n } from 'vue-i18n';
58
+ import {
59
+ useRepresentableAgentPauseCause,
60
+ } from '../../../../composables/useRepresentableAgentPauseCause/useRepresentableAgentPauseCause';
61
+
62
+ const props = defineProps({
63
+ options: {
64
+ type: Array,
65
+ default: () => [],
66
+ },
67
+ });
68
+
69
+ const emit = defineEmits(['change', 'close']);
70
+
71
+ const options = toRef(props, 'options');
72
+
73
+ const selected = ref('');
74
+
75
+ const { t } = useI18n();
76
+
77
+ const { representablePauseCause } = useRepresentableAgentPauseCause(options);
78
+
79
+ const pauseCause = computed(() => representablePauseCause.value.map((cause) => ({
80
+ ...cause,
81
+ duration: cause.isOverflow
82
+ ? `-${cause.durationMin - cause.limitMin} ${t('webitelUI.agentStatusSelect.pauseCausePopup.min')}`
83
+ : `${cause.durationMin} ${t('webitelUI.agentStatusSelect.pauseCausePopup.min')}`,
84
+ limit: cause.limitMin
85
+ ? `${cause.limitMin} ${t('webitelUI.agentStatusSelect.pauseCausePopup.min')}`
86
+ : t('webitelUI.agentStatusSelect.pauseCausePopup.unlimited'),
87
+ })));
88
+
89
+ function select(option) {
90
+ selected.value = option;
91
+ }
92
+
93
+ function close() {
94
+ emit('close');
95
+ }
96
+
97
+ function setPause() {
98
+ emit('change', selected.value.name);
99
+ close();
100
+ }
101
+ </script>
102
+
103
+ <style lang="scss" scoped>
104
+ .wt-cc-pause-cause-popup__option {
105
+ display: flex;
106
+ justify-content: space-between;
107
+ align-items: center;
108
+
109
+ &:not(:last-child) {
110
+ margin-bottom: var(--spacing-sm);
111
+ }
112
+ }
113
+
114
+ .wt-cc-pause-cause-popup__option__limits-wrapper {
115
+ & > span {
116
+ @extend %typo-caption;
117
+ padding: 6px 10px;
118
+ }
119
+
120
+ .wt-cc-pause-cause-popup__option__duration--overflow {
121
+ color: var(--false-color);
122
+ }
123
+ }
124
+ </style>
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <article class="wt-cc-agent-status-select">
3
+ <wt-status-select
4
+ class="wt-cc-agent-status-select__status-select"
5
+ :status="status"
6
+ :status-duration="statusDuration"
7
+ @change="handleStatusSelectInput"
8
+ ></wt-status-select>
9
+ <pause-cause-popup
10
+ v-if="isPauseCausePopup"
11
+ :options="pauseCauses"
12
+ @change="handlePauseCauseInput"
13
+ @close="closePauseCausePopup"
14
+ ></pause-cause-popup>
15
+ </article>
16
+ </template>
17
+
18
+ <script>
19
+ import AgentStatus from '@webitel/ui-sdk/src/enums/AgentStatus/AgentStatus.enum';
20
+ import AgentStatusAPI from '../api/agent-status';
21
+ import PauseCauseAPI from '../api/pause-cause';
22
+ import PauseCausePopup from './_internals/wt-cc-pause-cause-popup.vue';
23
+
24
+ export default {
25
+ name: 'wt-cc-agent-status-select',
26
+ components: { PauseCausePopup },
27
+ props: {
28
+ agentId: {
29
+ type: [String, Number],
30
+ },
31
+ status: {
32
+ // can be undefined, is agent wasn't loaded yet
33
+ default: AgentStatus.OFFLINE,
34
+ },
35
+ statusDuration: {
36
+ type: [Number, String],
37
+ default: 0,
38
+ },
39
+ },
40
+ data: () => ({
41
+ isPauseCausePopup: false,
42
+ pauseCauses: [],
43
+ }),
44
+ methods: {
45
+ async updateStatus({ agentId, status, pauseCause }) {
46
+ try {
47
+ await AgentStatusAPI.patch({ agentId, status, pauseCause });
48
+ } catch (err) {
49
+ throw err;
50
+ }
51
+ },
52
+ async handleStatusSelectInput(status) {
53
+ if (status === AgentStatus.PAUSE) {
54
+ await this.loadPauseCauses();
55
+ if (this.pauseCauses.length) {
56
+ this.openPauseCausePopup();
57
+ return;
58
+ }
59
+ }
60
+ if (status === this.status) return;
61
+ await this.changeStatus({ status });
62
+ },
63
+ handlePauseCauseInput(pauseCause) {
64
+ const status = AgentStatus.PAUSE;
65
+ this.changeStatus({ status, pauseCause });
66
+ },
67
+ async changeStatus({ status, pauseCause }) {
68
+ const statusPayload = { agentId: this.agentId, status, pauseCause };
69
+ try {
70
+ await this.updateStatus(statusPayload);
71
+ this.$emit('changed', statusPayload);
72
+ } catch (err) {
73
+ console.error(err);
74
+ }
75
+ },
76
+ async loadPauseCauses() {
77
+ this.isLoaded = false;
78
+ try {
79
+ const response = await PauseCauseAPI.getList({ agentId: this.agentId });
80
+ this.pauseCauses = response.items;
81
+ } catch {
82
+ }
83
+ this.isLoaded = true;
84
+ },
85
+ openPauseCausePopup() {
86
+ this.isPauseCausePopup = true;
87
+ },
88
+ closePauseCausePopup() {
89
+ this.isPauseCausePopup = false;
90
+ },
91
+ },
92
+ };
93
+ </script>
94
+
95
+ <style lang="scss" scoped>
96
+ .wt-cc-agent-status-select__status-select {
97
+ max-width: 200px;
98
+ }
99
+ </style>
@@ -0,0 +1,27 @@
1
+ import BaseStoreModule from '../BaseStoreModules/BaseStoreModule';
2
+
3
+ let interval = null;
4
+
5
+ export default class ReactiveNowStoreModule extends BaseStoreModule {
6
+ state = {
7
+ now: Date.now(),
8
+ };
9
+
10
+ actions = {
11
+ SET_NOW_WATCHER: (context) => {
12
+ interval = setInterval(() => {
13
+ context.commit('UPDATE_NOW');
14
+ }, 1000);
15
+ },
16
+
17
+ CLEAR_NOW_WATCHER: () => {
18
+ clearInterval(interval);
19
+ },
20
+ };
21
+
22
+ mutations = {
23
+ UPDATE_NOW: (state) => {
24
+ state.now = Date.now();
25
+ },
26
+ };
27
+ }