fetch only the edited view's count after saving a view

This commit is contained in:
Abhinav Raut
2026-09-02 01:19:50 +05:30
parent 5435d3e716
commit b136346b95
7 changed files with 73 additions and 11 deletions
+33
View File
@@ -264,6 +264,39 @@ func handleGetSidebarCounts(r *fastglue.Request) error {
return r.SendEnvelope(counts)
}
// handleGetViewCount returns the sidebar badge count for one view.
func handleGetViewCount(r *fastglue.Request) error {
var (
app = r.Context.(*App)
auser = r.RequestCtx.UserValue("user").(amodels.User)
viewID, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
)
if viewID < 1 {
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("globals.messages.somethingWentWrong"), nil, envelope.InputError)
}
view, err := app.view.Get(viewID)
if err != nil {
return sendErrorEnvelope(r, err)
}
user, err := app.user.GetAgentCachedOrLoad(auser.ID)
if err != nil {
return sendErrorEnvelope(r, err)
}
if !conversation.UserCanAccessView(view, auser.ID, user.Teams.IDs()) {
return r.SendErrorEnvelope(fasthttp.StatusForbidden, app.i18n.T("conversation.viewPermissionDenied"), nil, envelope.PermissionError)
}
count, err := app.conversation.GetViewCount(user.ID, user.Permissions, user.Teams.IDs(), view)
if err != nil {
return sendErrorEnvelope(r, err)
}
return r.SendEnvelope(map[string]int{"count": count})
}
// handleGetTeamUnassignedConversations returns conversations assigned to a team but not to any user.
func handleGetTeamUnassignedConversations(r *fastglue.Request) error {
var (
+1
View File
@@ -59,6 +59,7 @@ func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) {
g.GET("/api/v1/conversations/sidebar-counts", perm(handleGetSidebarCounts, "conversations:read"))
g.GET("/api/v1/teams/{id}/conversations/unassigned", perm(handleGetTeamUnassignedConversations, "conversations:read_team_inbox"))
g.GET("/api/v1/views/{id}/conversations", perm(handleGetViewConversations, "conversations:read"))
g.GET("/api/v1/views/{id}/count", perm(handleGetViewCount, "conversations:read"))
g.GET("/api/v1/conversations/{uuid}", perm(handleGetConversation, "conversations:read"))
g.GET("/api/v1/conversations/{uuid}/participants", perm(handleGetConversationParticipants, "conversations:read"))
g.PUT("/api/v1/conversations/{uuid}/assignee/user", perm(handleUpdateUserAssignee, "conversations:update_user_assignee"))
+2 -1
View File
@@ -261,7 +261,8 @@ const refreshViews = async (data) => {
// TODO: move model to constants.
if (data?.model === 'view') {
await getUserViews()
conversationStore.fetchSidebarCounts({ force: true })
if (data.id) conversationStore.fetchViewCount(data.id)
else conversationStore.fetchSidebarCounts({ force: true })
const openID = route.params.viewID
// If the open view was edited its filters may have changed, refetch.
if (openID && userViews.value.some((v) => String(v.id) === String(openID))) {
+2
View File
@@ -382,6 +382,7 @@ const getAllConversations = (params) =>
const getMentionedConversations = (params) =>
http.get('/api/v1/conversations/mentioned', { params, abortOnRoute: true })
const getSidebarCounts = () => http.get('/api/v1/conversations/sidebar-counts')
const getViewCount = (id) => http.get(`/api/v1/views/${id}/count`)
const getViewConversations = (id, params) =>
http.get(`/api/v1/views/${id}/conversations`, { params, abortOnRoute: true })
const uploadMedia = (data) =>
@@ -655,6 +656,7 @@ export default {
getAllConversations,
getMentionedConversations,
getSidebarCounts,
getViewCount,
getTeamUnassignedConversations,
getViewConversations,
getOverviewCharts,
@@ -163,18 +163,17 @@ const onSubmit = form.handleSubmit(async (values) => {
try {
const payload = { ...values, filters: serializeFilterTree(values.filters) }
if (payload.id) {
await api.updateView(payload.id, payload)
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
description: t('globals.messages.savedSuccessfully')
})
let viewID = payload.id
if (viewID) {
await api.updateView(viewID, payload)
} else {
await api.createView(payload)
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
description: t('globals.messages.savedSuccessfully')
})
const resp = await api.createView(payload)
viewID = resp?.data?.data?.id
}
emitter.emit(EMITTER_EVENTS.REFRESH_LIST, { model: 'view' })
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
description: t('globals.messages.savedSuccessfully')
})
emitter.emit(EMITTER_EVENTS.REFRESH_LIST, { model: 'view', id: viewID })
openDialog.value = false
form.resetForm()
} catch (error) {
@@ -86,6 +86,15 @@ export const useConversationStore = defineStore('conversation', () => {
return sidebarCountsRequest
}
async function fetchViewCount (viewID) {
try {
const resp = await api.getViewCount(viewID)
sidebarCounts.views[viewID] = resp?.data?.data?.count || 0
} catch {
// The sidebar works without counts.
}
}
// WS events burst one per conversation; leading + trailing keeps it to two requests per burst.
const SIDEBAR_COUNTS_EVENT_THROTTLE = 45_000
const refreshSidebarCounts = useThrottleFn(
@@ -1312,6 +1321,7 @@ export const useConversationStore = defineStore('conversation', () => {
isSelected,
sidebarCounts,
fetchSidebarCounts,
fetchViewCount,
refreshSidebarCounts
}
})
+16
View File
@@ -53,6 +53,22 @@ func (c *Manager) GetSidebarCounts(viewingUserID int, permissions []string, team
return out, nil
}
// GetViewCount returns the capped open count for one view.
func (c *Manager) GetViewCount(viewingUserID int, permissions []string, teamIDs []int, view vmodels.View) (int, error) {
lists := ListsForUserPermissions(permissions)
if len(lists) == 0 {
return 0, nil
}
ctx, cancel := context.WithTimeout(context.Background(), sidebarCountsQueryTimeout)
defer cancel()
counts, err := c.getViewCounts(ctx, viewingUserID, teamIDs, lists, []vmodels.View{view})
if err != nil {
return 0, err
}
return counts[view.ID], nil
}
func (c *Manager) fillStandardSidebarCounts(ctx context.Context, out *models.SidebarCounts, userID int, permissions []string) error {
var row struct {
Assigned int `db:"assigned"`