feat: Add the ability for the agents to execute a macro (#5698)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Fayaz Ahmed
2022-10-25 09:03:59 +05:30
committed by GitHub
parent d54392cb53
commit c3ec1d4f8a
14 changed files with 431 additions and 14 deletions

View File

@@ -93,6 +93,19 @@
/>
</accordion-item>
</div>
<woot-feature-toggle
v-else-if="element.name === 'macros'"
feature-key="macros"
>
<accordion-item
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.MACROS')"
:is-open="isContactSidebarItemOpen('is_macro_open')"
compact
@click="value => toggleSidebarUIState('is_macro_open', value)"
>
<macros-list :conversation-id="conversationId" />
</accordion-item>
</woot-feature-toggle>
</div>
</transition-group>
</draggable>
@@ -112,6 +125,7 @@ import CustomAttributes from './customAttributes/CustomAttributes.vue';
import CustomAttributeSelector from './customAttributes/CustomAttributeSelector.vue';
import draggable from 'vuedraggable';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import MacrosList from './Macros/List';
export default {
components: {
@@ -123,6 +137,7 @@ export default {
CustomAttributeSelector,
ConversationAction,
draggable,
MacrosList,
},
mixins: [alertMixin, uiSettingsMixin],
props: {

View File

@@ -0,0 +1,75 @@
<template>
<div>
<div
v-if="!uiFlags.isFetching && !macros.length"
class="macros_list--empty-state"
>
<p class="no-items-error-message">
{{ $t('MACROS.LIST.404') }}
</p>
<router-link :to="addAccountScoping('settings/macros')">
<woot-button
variant="smooth"
icon="add"
size="tiny"
class="macros_add-button"
>
{{ $t('MACROS.HEADER_BTN_TXT') }}
</woot-button>
</router-link>
</div>
<woot-loading-state
v-if="uiFlags.isFetching"
:message="$t('MACROS.LOADING')"
/>
<div v-if="!uiFlags.isFetching && macros.length" class="macros-list">
<macro-item
v-for="macro in macros"
:key="macro.id"
:macro="macro"
:conversation-id="conversationId"
/>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import MacroItem from './MacroItem';
import accountMixin from 'dashboard/mixins/account.js';
export default {
components: {
MacroItem,
},
mixins: [accountMixin],
props: {
conversationId: {
type: [Number, String],
required: true,
},
},
computed: {
...mapGetters({
macros: ['macros/getMacros'],
uiFlags: 'macros/getUIFlags',
}),
},
mounted() {
this.$store.dispatch('macros/get');
},
};
</script>
<style scoped lang="scss">
.macros-list {
padding: var(--space-smaller);
}
.macros_list--empty-state {
padding: var(--space-slab);
p {
margin: 0;
}
}
.macros_add-button {
margin: var(--space-small) auto 0;
}
</style>

View File

@@ -0,0 +1,111 @@
<template>
<div class="macro">
<span class="text-truncate">{{ macro.name }}</span>
<div class="macros-actions">
<woot-button
v-tooltip.left-start="$t('MACROS.EXECUTE.PREVIEW')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="info"
class="margin-right-smaller"
@click="toggleMacroPreview(macro)"
/>
<woot-button
v-tooltip.left-start="$t('MACROS.EXECUTE.BUTTON_TOOLTIP')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="play-circle"
:is-loading="isExecuting"
@click="executeMacro(macro)"
/>
</div>
<transition name="menu-slide">
<macro-preview
v-if="showPreview"
v-on-clickaway="closeMacroPreview"
:macro="macro"
/>
</transition>
</div>
</template>
<script>
import alertMixin from 'shared/mixins/alertMixin';
import { mixin as clickaway } from 'vue-clickaway';
import MacroPreview from './MacroPreview';
export default {
components: {
MacroPreview,
},
mixins: [alertMixin, clickaway],
props: {
macro: {
type: Object,
required: true,
},
conversationId: {
type: [Number, String],
required: true,
},
},
data() {
return {
isExecuting: false,
showPreview: false,
};
},
methods: {
async executeMacro(macro) {
try {
this.isExecuting = true;
await this.$store.dispatch('macros/execute', {
macroId: macro.id,
conversationIds: [this.conversationId],
});
this.showAlert(this.$t('MACROS.EXECUTE.EXECUTED_SUCCESSFULLY'));
} catch (error) {
this.showAlert(this.$t('MACROS.ERROR'));
} finally {
this.isExecuting = false;
}
},
toggleMacroPreview() {
this.showPreview = !this.showPreview;
},
closeMacroPreview() {
this.showPreview = false;
},
},
};
</script>
<style scoped lang="scss">
.macro {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
margin: 0;
padding: var(--space-small);
font-weight: var(--font-weight-medium);
border-radius: var(--border-radius-normal);
color: var(--s-700);
&:hover {
background: var(--s-25);
color: var(--s-600);
}
&:focus {
border-color: var(--w-300);
}
.macros-actions {
display: flex;
align-items: center;
justify-content: flex-end;
}
}
</style>

View File

@@ -0,0 +1,116 @@
<template>
<div class="macro-preview">
<p class="macro-title">{{ macro.name }}</p>
<div v-for="(action, i) in resolvedMacro" :key="i" class="macro-block">
<div v-if="i !== macro.actions.length - 1" class="macro-block-border" />
<div class="macro-block-dot" />
<p class="macro-action-name">{{ action.actionName }}</p>
<p class="macro-action-params">{{ action.actionValue }}</p>
</div>
</div>
</template>
<script>
import {
resolveActionName,
resolveTeamIds,
resolveLabels,
} from 'dashboard/routes/dashboard/settings/macros/macroHelper';
import { mapGetters } from 'vuex';
export default {
props: {
macro: {
type: Object,
required: true,
},
},
computed: {
resolvedMacro() {
return this.macro.actions.map(action => {
return {
actionName: resolveActionName(action.action_name),
actionValue: this.getActionValue(
action.action_name,
action.action_params
),
};
});
},
...mapGetters({
labels: 'labels/getLabels',
teams: 'teams/getTeams',
}),
},
methods: {
getActionValue(key, params) {
const actionsMap = {
assign_team: resolveTeamIds(this.teams, params),
add_label: resolveLabels(this.labels, params),
mute_conversation: null,
snooze_conversation: null,
resolve_conversation: null,
send_webhook_event: params[0],
send_message: params[0],
send_email_transcript: params[0],
};
return actionsMap[key] || '';
},
},
};
</script>
<style lang="scss" scoped>
.macro-preview {
position: absolute;
max-height: calc(var(--space-giga) * 1.5);
min-height: var(--space-jumbo);
width: calc(var(--space-giga) + var(--space-large));
border-radius: var(--border-radius-normal);
background-color: var(--white);
box-shadow: var(--shadow-dropdown-pane);
bottom: calc(var(--space-three) + var(--space-half));
right: calc(var(--space-three) + var(--space-half));
overflow-y: auto;
padding: var(--space-slab);
.macro-title {
margin-bottom: var(--space-slab);
color: var(--s-900);
font-weight: var(--font-weight-bold);
}
.macro-block {
position: relative;
padding-left: var(--space-slab);
&:not(:last-child) {
padding-bottom: var(--space-slab);
}
.macro-block-border {
top: 0.625rem;
position: absolute;
bottom: var(--space-minus-half);
left: 0;
width: 1px;
background-color: var(--s-100);
}
.macro-block-dot {
position: absolute;
left: -0.35rem;
height: var(--space-small);
width: var(--space-small);
border: 2px solid var(--s-100);
background-color: var(--white);
border-radius: var(--border-radius-full);
top: 0.4375rem;
}
}
.macro-action-name {
font-size: var(--font-size-mini);
color: var(--s-500);
}
}
</style>

View File

@@ -1,3 +1,4 @@
import { MACRO_ACTION_TYPES as macroActionTypes } from 'dashboard/routes/dashboard/settings/macros/constants.js';
export const emptyMacro = {
name: '',
actions: [
@@ -8,3 +9,25 @@ export const emptyMacro = {
],
visibility: 'global',
};
export const resolveActionName = key => {
return macroActionTypes.find(i => i.key === key).label;
};
export const resolveTeamIds = (teams, ids) => {
return ids
.map(id => {
const team = teams.find(i => i.id === id);
return team ? team.name : '';
})
.join(', ');
};
export const resolveLabels = (labels, ids) => {
return ids
.map(id => {
const label = labels.find(i => i.title === id);
return label ? label.title : '';
})
.join(', ');
};