Skip to content

Commit 1c6d03a

Browse files
committed
Merge branch 'main' into feature/conversation-last-reply
2 parents ac61d43 + c434de1 commit 1c6d03a

File tree

13 files changed

+144
-45
lines changed

13 files changed

+144
-45
lines changed

cmd/conversation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ func handleUpdateConversationtags(r *fastglue.Request) error {
472472
return sendErrorEnvelope(r, err)
473473
}
474474

475-
if err := app.conversation.UpsertConversationTags(uuid, tagNames, user); err != nil {
475+
if err := app.conversation.SetConversationTags(uuid, models.ActionSetTags, tagNames, user); err != nil {
476476
return sendErrorEnvelope(r, err)
477477
}
478478
return r.SendEnvelope(true)

cmd/macro.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ func isMacroActionAllowed(action string) bool {
292292
switch action {
293293
case autoModels.ActionSendPrivateNote, autoModels.ActionReply:
294294
return false
295-
case autoModels.ActionAssignTeam, autoModels.ActionAssignUser, autoModels.ActionSetStatus, autoModels.ActionSetPriority, autoModels.ActionSetTags:
295+
case autoModels.ActionAssignTeam, autoModels.ActionAssignUser, autoModels.ActionSetStatus, autoModels.ActionSetPriority, autoModels.ActionAddTags, autoModels.ActionSetTags, autoModels.ActionRemoveTags:
296296
return true
297297
default:
298298
return false

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ services:
3636
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-libredesk}
3737
POSTGRES_DB: ${POSTGRES_DB:-libredesk}
3838
healthcheck:
39-
test: ["CMD-SHELL", "pg_isready -U libredesk"]
39+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-libredesk} -d ${POSTGRES_DB:-libredesk}"]
4040
interval: 10s
4141
timeout: 5s
4242
retries: 6
@@ -60,4 +60,4 @@ networks:
6060

6161
volumes:
6262
postgres-data:
63-
redis-data:
63+
redis-data:

frontend/src/composables/useConversationFilters.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,17 @@ export function useConversationFilters () {
221221
type: FIELD_TYPE.SELECT,
222222
options: slaStore.options
223223
},
224+
add_tags: {
225+
label: 'Add tags',
226+
type: FIELD_TYPE.TAG
227+
},
224228
set_tags: {
225229
label: 'Set tags',
226230
type: FIELD_TYPE.TAG
231+
},
232+
remove_tags: {
233+
label: 'Remove tags',
234+
type: FIELD_TYPE.TAG
227235
}
228236
}))
229237

@@ -248,9 +256,17 @@ export function useConversationFilters () {
248256
type: FIELD_TYPE.SELECT,
249257
options: cStore.priorityOptions
250258
},
259+
add_tags: {
260+
label: 'Add tags',
261+
type: FIELD_TYPE.TAG
262+
},
251263
set_tags: {
252264
label: 'Set tags',
253265
type: FIELD_TYPE.TAG
266+
},
267+
remove_tags: {
268+
label: 'Remove tags',
269+
type: FIELD_TYPE.TAG
254270
}
255271
}))
256272

frontend/src/features/command/CommandBox.vue

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,21 @@
123123
:size="10"
124124
class="shrink-0 text-primary"
125125
/>
126+
<Tags
127+
v-else-if="action.type === 'add_tags'"
128+
:size="10"
129+
class="shrink-0 text-primary"
130+
/>
126131
<Tags
127132
v-else-if="action.type === 'set_tags'"
128133
:size="10"
129134
class="shrink-0 text-primary"
130135
/>
136+
<Tags
137+
v-else-if="action.type === 'remove_tags'"
138+
:size="10"
139+
class="shrink-0 text-primary"
140+
/>
131141
</div>
132142
<span class="truncate">{{ getActionLabel(action) }}</span>
133143
</div>
@@ -260,7 +270,9 @@ const getActionLabel = computed(() => (action) => {
260270
assign_team: t('globals.messages.assignTeam'),
261271
set_status: t('globals.messages.setStatus'),
262272
set_priority: t('globals.messages.setPriority'),
263-
set_tags: t('globals.messages.setTags')
273+
add_tags: t('globals.messages.addTags'),
274+
set_tags: t('globals.messages.setTags'),
275+
remove_tags: t('globals.messages.removeTags')
264276
}
265277
return `${prefixes[action.type]}: ${action.display_value.length > 0 ? action.display_value.join(', ') : action.value.join(', ')}`
266278
})

frontend/src/features/conversation/MacroActionsPreview.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ const getIcon = (type) =>
6060
assign_user: User,
6161
set_status: MessageSquare,
6262
set_priority: Flag,
63-
set_tags: Tags
63+
add_tags: Tags,
64+
set_tags: Tags,
65+
remove_tags: Tags
6466
})[type]
6567
6668
const getDisplayValue = (action) => {
@@ -80,8 +82,12 @@ const getTooltip = (action) => {
8082
return `${t('globals.messages.setStatus')}: ${getDisplayValue(action)}`
8183
case 'set_priority':
8284
return `${t('globals.messages.setPriority')}: ${getDisplayValue(action)}`
85+
case 'add_tags':
86+
return `${t('globals.messages.addTags')}: ${getDisplayValue(action)}`
8387
case 'set_tags':
8488
return `${t('globals.messages.setTags')}: ${getDisplayValue(action)}`
89+
case 'remove_tags':
90+
return `${t('globals.messages.removeTags')}: ${getDisplayValue(action)}`
8591
default:
8692
return `${t('globals.terms.action')}: ${action.type}, ${t('globals.terms.value')}: ${getDisplayValue(action)}`
8793
}

frontend/src/utils/sla.js

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,37 @@ import { differenceInMinutes } from 'date-fns'
1212
export function calculateSla (dueAt, actualAt) {
1313
const compareTime = actualAt ? new Date(actualAt) : new Date()
1414
const dueTime = new Date(dueAt)
15+
// Difference in minutes will be negative if overdue, positive if remaining.
1516
const diffInMinutes = differenceInMinutes(dueTime, compareTime)
1617

17-
if (!actualAt) {
18-
if (diffInMinutes > 0) {
19-
if (diffInMinutes >= 2880) {
20-
return {
21-
status: 'remaining',
22-
value: `${Math.floor(diffInMinutes / 1440)} days`
23-
}
24-
}
18+
// No actual at and diffInMinutes is positive; there is still time remaining.
19+
if (!actualAt && diffInMinutes >= 0) {
20+
if (diffInMinutes >= 2880) {
2521
return {
2622
status: 'remaining',
27-
value: diffInMinutes < 60 ? `${diffInMinutes} mins` : `${Math.floor(diffInMinutes / 60)} hrs`
23+
value: `${Math.floor(diffInMinutes / 1440)} days`
2824
}
2925
}
26+
return {
27+
status: 'remaining',
28+
value: diffInMinutes < 60 ? `${diffInMinutes} mins` : `${Math.floor(diffInMinutes / 60)} hrs`
29+
}
3030
}
3131

32-
const overdueTime = Math.abs(diffInMinutes)
33-
const status = actualAt ? 'hit' : 'overdue'
32+
let status = 'hit'
33+
if (diffInMinutes < 0) {
34+
status = 'overdue'
35+
}
3436

35-
if (overdueTime >= 2880) {
37+
const overdueMins = Math.abs(diffInMinutes)
38+
if (overdueMins >= 2880) {
3639
return {
3740
status,
38-
value: `${Math.floor(overdueTime / 1440)} days`
41+
value: `${Math.floor(overdueMins / 1440)} days`
3942
}
4043
}
4144
return {
4245
status,
43-
value: overdueTime < 60 ? `${overdueTime} mins` : `${Math.floor(overdueTime / 60)} hrs`
46+
value: overdueMins < 60 ? `${overdueMins} mins` : `${Math.floor(overdueMins / 60)} hrs`
4447
}
4548
}

i18n/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@
147147
"globals.messages.assignTeam": "Assign to team",
148148
"globals.messages.setStatus": "Set status",
149149
"globals.messages.setPriority": "Set priority",
150+
"globals.messages.addTags": "Add tags",
150151
"globals.messages.setTags": "Set tags",
152+
"globals.messages.removeTags": "Remove tags",
151153
"globals.messages.snooze": "Snooze",
152154
"globals.messages.resolve": "Resolve",
153155
"globals.messages.applyMacro": "Apply macro",

internal/automation/models/models.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ const (
1616
ActionSendPrivateNote = "send_private_note"
1717
ActionReply = "send_reply"
1818
ActionSetSLA = "set_sla"
19+
ActionAddTags = "add_tags"
1920
ActionSetTags = "set_tags"
21+
ActionRemoveTags = "remove_tags"
2022
ActionSendCSAT = "send_csat"
2123

2224
OperatorAnd = "AND"
@@ -70,7 +72,9 @@ var ActionPermissions = map[string]string{
7072
ActionSetPriority: authzModels.PermConversationsUpdatePriority,
7173
ActionSendPrivateNote: authzModels.PermMessagesWrite,
7274
ActionReply: authzModels.PermMessagesWrite,
75+
ActionAddTags: authzModels.PermConversationsUpdateTags,
7376
ActionSetTags: authzModels.PermConversationsUpdateTags,
77+
ActionRemoveTags: authzModels.PermConversationsUpdateTags,
7478
}
7579

7680
// RuleRecord represents a rule record in the database

internal/conversation/conversation.go

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,9 @@ type queries struct {
204204
UpdateConversationLastMessage *sqlx.Stmt `query:"update-conversation-last-message"`
205205
InsertConversationParticipant *sqlx.Stmt `query:"insert-conversation-participant"`
206206
InsertConversation *sqlx.Stmt `query:"insert-conversation"`
207-
UpsertConversationTags *sqlx.Stmt `query:"upsert-conversation-tags"`
207+
AddConversationTags *sqlx.Stmt `query:"add-conversation-tags"`
208+
SetConversationTags *sqlx.Stmt `query:"set-conversation-tags"`
209+
RemoveConversationTags *sqlx.Stmt `query:"remove-conversation-tags"`
208210
GetConversationTags *sqlx.Stmt `query:"get-conversation-tags"`
209211
UnassignOpenConversations *sqlx.Stmt `query:"unassign-open-conversations"`
210212
ReOpenConversation *sqlx.Stmt `query:"re-open-conversation"`
@@ -673,32 +675,62 @@ func (c *Manager) GetDashboardChart(userID, teamID int) (json.RawMessage, error)
673675
return stats, nil
674676
}
675677

676-
// UpsertConversationTags upserts the tags associated with a conversation.
677-
func (c *Manager) UpsertConversationTags(uuid string, tagNames []string, actor umodels.User) error {
678-
// Get the existing tags.
679-
tags, err := c.getConversationTags(uuid)
678+
// SetConversationTags sets the tags associated with a conversation.
679+
func (c *Manager) SetConversationTags(uuid string, action string, tagNames []string, actor umodels.User) error {
680+
// Get current tags list.
681+
prevTags, err := c.getConversationTags(uuid)
680682
if err != nil {
681-
return envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.tag}"), nil)
683+
return envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.tag}"), nil)
682684
}
683685

684-
// Upsert the tags.
685-
if _, err := c.q.UpsertConversationTags.Exec(uuid, pq.Array(tagNames)); err != nil {
686-
c.lo.Error("error upserting conversation tags", "error", err)
687-
return envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.tag}"), nil)
686+
// Add specified tags, ignore existing ones.
687+
if action == amodels.ActionAddTags {
688+
if _, err := c.q.AddConversationTags.Exec(uuid, pq.Array(tagNames)); err != nil {
689+
c.lo.Error("error adding conversation tags", "error", err)
690+
return envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.tag}"), nil)
691+
}
688692
}
689693

690-
// Find the newly added tags.
691-
newTags := []string{}
692-
for _, tag := range tagNames {
693-
if slices.Contains(tags, tag) {
694+
// Set specified tags and remove all other existing ones.
695+
if action == amodels.ActionSetTags {
696+
if _, err := c.q.SetConversationTags.Exec(uuid, pq.Array(tagNames)); err != nil {
697+
c.lo.Error("error setting conversation tags", "error", err)
698+
return envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.tag}"), nil)
699+
}
700+
}
701+
702+
// Delete specified tags, ignore all others.
703+
if action == amodels.ActionRemoveTags {
704+
if _, err := c.q.RemoveConversationTags.Exec(uuid, pq.Array(tagNames)); err != nil {
705+
c.lo.Error("error removing conversation tags", "error", err)
706+
return envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.tag}"), nil)
707+
}
708+
}
709+
710+
// Get updated tags list.
711+
newTags, err := c.getConversationTags(uuid)
712+
if err != nil {
713+
return envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.tag}"), nil)
714+
}
715+
716+
// Find actually removed tags.
717+
for _, tag := range prevTags {
718+
if slices.Contains(newTags, tag) {
694719
continue
695720
}
696-
newTags = append(newTags, tag)
721+
// Record the removed tags as activities.
722+
if err := c.RecordTagRemoval(uuid, tag, actor); err != nil {
723+
return envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.tag}"), nil)
724+
}
697725
}
698726

699-
// Record the new tags as activities.
727+
// Find actually added tags.
700728
for _, tag := range newTags {
701-
if err := c.RecordTagChange(uuid, tag, actor); err != nil {
729+
if slices.Contains(prevTags, tag) {
730+
continue
731+
}
732+
// Record the added tags as activities.
733+
if err := c.RecordTagAddition(uuid, tag, actor); err != nil {
702734
return envelope.NewError(envelope.GeneralError, c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.tag}"), nil)
703735
}
704736
}
@@ -858,8 +890,8 @@ func (m *Manager) ApplyAction(action amodels.RuleAction, conv models.Conversatio
858890
case amodels.ActionSetSLA:
859891
slaID, _ := strconv.Atoi(action.Value[0])
860892
return m.ApplySLA(conv, slaID, user)
861-
case amodels.ActionSetTags:
862-
return m.UpsertConversationTags(conv.UUID, action.Value, user)
893+
case amodels.ActionAddTags, amodels.ActionSetTags, amodels.ActionRemoveTags:
894+
return m.SetConversationTags(conv.UUID, action.Type, action.Value, user)
863895
case amodels.ActionSendCSAT:
864896
return m.SendCSATReply(user.ID, conv)
865897
default:

0 commit comments

Comments
 (0)