feat: run macros from the command bar (#15422)
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import { useMacroExecution } from 'dashboard/composables/useMacroExecution';
|
||||
import { useOrderedMacros } from 'dashboard/composables/useOrderedMacros';
|
||||
import { usePolicy } from 'dashboard/composables/usePolicy';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { ICON_TOY_BRICK } from 'dashboard/helper/commandbar/icons';
|
||||
import {
|
||||
isAConversationRoute,
|
||||
isAInboxViewRoute,
|
||||
} from 'dashboard/helper/routeHelpers';
|
||||
|
||||
export function useMacroHotKeys() {
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
|
||||
const { orderedMacros } = useOrderedMacros();
|
||||
const { execute, submitPendingAttributes, dismissPendingAttributes } =
|
||||
useMacroExecution();
|
||||
const { isFeatureFlagEnabled } = usePolicy();
|
||||
|
||||
const currentChat = useMapGetter('getSelectedChat');
|
||||
const pendingAttributes = ref(null);
|
||||
|
||||
const isMacrosAvailable = computed(
|
||||
() =>
|
||||
isFeatureFlagEnabled(FEATURE_FLAGS.MACROS) &&
|
||||
(isAConversationRoute(route.name) || isAInboxViewRoute(route.name))
|
||||
);
|
||||
|
||||
watch(
|
||||
isMacrosAvailable,
|
||||
isActive => {
|
||||
if (isActive && !orderedMacros.value.length) store.dispatch('macros/get');
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const macroHotKeys = computed(() => {
|
||||
if (!isMacrosAvailable.value || !orderedMacros.value.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const options = orderedMacros.value.map(macro => ({
|
||||
id: `macro-${macro.id}`,
|
||||
title: macro.name,
|
||||
parent: 'execute_a_macro',
|
||||
section: t('COMMAND_BAR.SECTIONS.EXECUTE_MACRO'),
|
||||
icon: ICON_TOY_BRICK,
|
||||
handler: () => {
|
||||
pendingAttributes.value = execute(macro, currentChat.value.id);
|
||||
},
|
||||
}));
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'execute_a_macro',
|
||||
title: t('COMMAND_BAR.COMMANDS.EXECUTE_A_MACRO'),
|
||||
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
|
||||
icon: ICON_TOY_BRICK,
|
||||
children: options.map(option => option.id),
|
||||
},
|
||||
...options,
|
||||
];
|
||||
});
|
||||
|
||||
return {
|
||||
macroHotKeys,
|
||||
pendingAttributes,
|
||||
submitPendingAttributes,
|
||||
dismissPendingAttributes,
|
||||
};
|
||||
}
|
||||
@@ -161,6 +161,7 @@
|
||||
"CHANGE_PRIORITY": "Change Priority",
|
||||
"CHANGE_TEAM": "Change Team",
|
||||
"SNOOZE_CONVERSATION": "Snooze Conversation",
|
||||
"EXECUTE_MACRO": "Execute Macro",
|
||||
"ADD_LABEL": "Add label to the conversation",
|
||||
"REMOVE_LABEL": "Remove label from the conversation",
|
||||
"SETTINGS": "Settings",
|
||||
@@ -206,6 +207,7 @@
|
||||
"AI_ASSIST": "AI Assist",
|
||||
"ASSIGN_PRIORITY": "Assign priority",
|
||||
"ASSIGN_A_TEAM": "Assign a team",
|
||||
"EXECUTE_A_MACRO": "Execute a macro",
|
||||
"MUTE_CONVERSATION": "Mute conversation",
|
||||
"UNMUTE_CONVERSATION": "Unmute conversation",
|
||||
"REMOVE_LABEL_FROM_CONVERSATION": "Remove label from the conversation",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import '@chatwoot/ninja-keys';
|
||||
import { ref, toRef, computed, watchEffect, onMounted } from 'vue';
|
||||
import { ref, toRef, computed, watch, watchEffect, onMounted } from 'vue';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -10,6 +10,8 @@ import { useInboxHotKeys } from 'dashboard/composables/commands/useInboxHotKeys'
|
||||
import { useGoToCommandHotKeys } from 'dashboard/composables/commands/useGoToCommandHotKeys';
|
||||
import { useBulkActionsHotKeys } from 'dashboard/composables/commands/useBulkActionsHotKeys';
|
||||
import { useConversationHotKeys } from 'dashboard/composables/commands/useConversationHotKeys';
|
||||
import { useMacroHotKeys } from 'dashboard/composables/commands/useMacroHotKeys';
|
||||
import ConversationResolveAttributesModal from 'dashboard/components-next/ConversationWorkflow/ConversationResolveAttributesModal.vue';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import {
|
||||
GENERAL_EVENTS,
|
||||
@@ -36,6 +38,7 @@ const { t, tm } = useI18n();
|
||||
const { resolvedLocale } = useLocale();
|
||||
|
||||
const ninjakeys = ref(null);
|
||||
const resolveAttributesModalRef = ref(null);
|
||||
|
||||
// Added selectedSnoozeType to track the selected snooze type
|
||||
// So if the selected snooze type is "custom snooze" then we set selectedSnoozeType with the CMD action id
|
||||
@@ -49,6 +52,21 @@ const { goToCommandHotKeys } = useGoToCommandHotKeys(
|
||||
);
|
||||
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
|
||||
const { conversationHotKeys } = useConversationHotKeys();
|
||||
const {
|
||||
macroHotKeys,
|
||||
pendingAttributes,
|
||||
submitPendingAttributes,
|
||||
dismissPendingAttributes,
|
||||
} = useMacroHotKeys();
|
||||
|
||||
watch(pendingAttributes, pending => {
|
||||
if (pending) {
|
||||
resolveAttributesModalRef.value?.open(
|
||||
pending.missing,
|
||||
pending.customAttributes
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const SNOOZE_PARENT_IDS = [
|
||||
'snooze_conversation',
|
||||
@@ -82,6 +100,7 @@ const hotKeys = computed(() => {
|
||||
...goToAppearanceHotKeys.value,
|
||||
...bulkActionsHotKeys.value,
|
||||
...conversationHotKeys.value,
|
||||
...macroHotKeys.value,
|
||||
];
|
||||
// When dynamic NLP snooze suggestions exist, hide all preset snooze actions to avoid duplication
|
||||
if (!dynamicSnoozeActions.value.length) return allActions;
|
||||
@@ -241,6 +260,11 @@ onMounted(() => {
|
||||
@selected="onSelected"
|
||||
@closed="onClosed"
|
||||
/>
|
||||
<ConversationResolveAttributesModal
|
||||
ref="resolveAttributesModalRef"
|
||||
@submit="submitPendingAttributes"
|
||||
@close="dismissPendingAttributes"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -32,8 +32,11 @@ const hotKeySources = {
|
||||
{ id: 'until_tomorrow', parent: 'snooze_notification' },
|
||||
{ id: 'until_tomorrow', parent: 'other_menu' },
|
||||
],
|
||||
macroHotKeys: [{ id: 'execute_a_macro' }],
|
||||
};
|
||||
|
||||
const pendingAttributes = ref(null);
|
||||
|
||||
vi.mock('dashboard/composables/commands/useAppearanceHotKeys', () => ({
|
||||
useAppearanceHotKeys: () => ({
|
||||
goToAppearanceHotKeys: ref(hotKeySources.goToAppearanceHotKeys),
|
||||
@@ -66,6 +69,15 @@ vi.mock('dashboard/composables/commands/useConversationHotKeys', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('dashboard/composables/commands/useMacroHotKeys', () => ({
|
||||
useMacroHotKeys: () => ({
|
||||
macroHotKeys: ref(hotKeySources.macroHotKeys),
|
||||
pendingAttributes,
|
||||
submitPendingAttributes: vi.fn(),
|
||||
dismissPendingAttributes: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const suggestion = {
|
||||
label: 'tomorrow',
|
||||
formattedDate: 'Feb 3, 9:00 AM',
|
||||
@@ -100,7 +112,10 @@ describe('commandbar', () => {
|
||||
let element;
|
||||
|
||||
const mountCommandBar = async (props = {}) => {
|
||||
wrapper = mount(CommandBar, { props });
|
||||
wrapper = mount(CommandBar, {
|
||||
props,
|
||||
global: { stubs: { ConversationResolveAttributesModal: true } },
|
||||
});
|
||||
await flushPromises();
|
||||
element = wrapper.find('ninja-keys').element;
|
||||
return wrapper;
|
||||
@@ -151,6 +166,7 @@ describe('commandbar', () => {
|
||||
'goto',
|
||||
'bulk',
|
||||
'conversation',
|
||||
'execute_a_macro',
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user