Skip to main content
Glama
Notifications.vue5.48 kB
<template> <button v-if="numberICanApprove > 0 || currentChangeSetNeedsApproval" v-tooltip="{ content: tooltipText, theme: 'notifications', }" :class=" clsx( 'relative h-full flex flex-row gap-2xs items-center children:pointer-events-none font-bold', numberICanApprove > 0 || currentChangeSetNeedsApproval ? 'bg-destructive-900' : 'hover:bg-black', (numberICanApprove > 0 || currentChangeSetNeedsApproval) && !compact ? 'p-xs' : 'p-sm', ) " @click="handleNotificationClick" > <Icon name="bell" :class=" clsx( numberICanApprove > 0 || currentChangeSetNeedsApproval ? 'text-destructive-500' : 'text-shade-0', ) " /> <template v-if=" (numberICanApprove > 0 || currentChangeSetNeedsApproval) && !compact " > <PillCounter :count=" Math.max(numberICanApprove, currentChangeSetNeedsApproval ? 1 : 0) " noColorStyles hideIfZero class="bg-destructive-500 py-2xs" /> <div class="text-xs"> Approval{{ numberICanApprove > 1 || (numberICanApprove === 0 && currentChangeSetNeedsApproval) ? "s" : "" }} </div> </template> <ApprovalPendingModal v-if="numberICanApprove > 0 || currentChangeSetNeedsApproval" ref="pendingApprovalModalRef" :changeSetsNeedingApproval="changeSetsNeedingApproval" /> </button> </template> <script setup lang="ts"> import clsx from "clsx"; import { computed, ref, onMounted, onBeforeUnmount } from "vue"; import { Icon, PillCounter } from "@si/vue-lib/design-system"; import { useQueries } from "@tanstack/vue-query"; import { ChangeSetId, ChangeSet, ChangeSetStatus, } from "@/api/sdf/dal/change_set"; import { ApprovalData } from "../types"; import ApprovalPendingModal from "./ApprovalPendingModal.vue"; import { useContext } from "../logic_composables/context"; import { approverForChangeSet } from "../logic_composables/change_set"; import { useApi, routes, apiContextForChangeSet } from "../api_composables"; const props = defineProps<{ changeSetsNeedingApproval: ChangeSet[]; }>(); const pendingApprovalModalRef = ref<InstanceType< typeof ApprovalPendingModal > | null>(null); const ctx = useContext(); const queries = computed(() => props.changeSetsNeedingApproval.map((changeSet) => { const changeSetId = changeSet.id; return { // TODO(nick): use the approvals enabled feature flag // NOTE(nick): this needs a different query key than the individual query because they // return different payloads. Without this, users of both queries will get colliding data. It // may be possible to unite them with the same query key to avoid calling the same route // twice. queryKey: ["approvalstatusbychangesetid", changeSetId], queryFn: async () => { const apiCtx = apiContextForChangeSet(ctx, changeSetId); const api = useApi(apiCtx); const call = api.endpoint<ApprovalData>(routes.ChangeSetApprovalStatus); const response = await call.get(); if (api.ok(response)) { return { changeSetId, approvalData: response.data }; } return undefined; }, }; }), ); const allApprovalDataQueries = useQueries({ queries, }); const allApprovalData = computed(() => { const results: Record<ChangeSetId, ApprovalData> = {}; for (const approvalDataQuery of allApprovalDataQueries.value) { if (approvalDataQuery.data) results[approvalDataQuery.data.changeSetId] = approvalDataQuery.data.approvalData; } return results; }); const numberICanApprove = computed(() => { let approvable = 0; props.changeSetsNeedingApproval.forEach((changeSet) => { const approvalData = allApprovalData.value[changeSet.id]; if (!approvalData || !ctx.user) return; if (approverForChangeSet(ctx.user.pk, approvalData)) approvable++; }); return approvable; }); const currentChangeSetNeedsApproval = computed(() => { return ctx.changeSet.value?.status === ChangeSetStatus.NeedsApproval; }); const tooltipText = computed(() => { if (numberICanApprove.value === 1) { return "You have a Change Set to approve."; } else if (numberICanApprove.value > 1) { return `You have ${numberICanApprove.value} Change Sets to approve.`; } else if (currentChangeSetNeedsApproval.value) { return "This change set requires approval before it can be applied."; } else { return "No Notifications"; } }); const windowWidth = ref(window.innerWidth); const windowResizeHandler = () => { windowWidth.value = window.innerWidth; }; onMounted(() => { windowResizeHandler(); window.addEventListener("resize", windowResizeHandler); }); onBeforeUnmount(() => { window.removeEventListener("resize", windowResizeHandler); }); const compact = computed(() => windowWidth.value < 850); const openPendingApprovalsModal = () => { if (numberICanApprove.value > 0 || currentChangeSetNeedsApproval.value) { pendingApprovalModalRef.value?.open(); } }; const handleNotificationClick = () => { if (numberICanApprove.value > 0 || currentChangeSetNeedsApproval.value) { // Open the approvals modal if you have change sets to approve OR if current change set needs approval openPendingApprovalsModal(); } }; </script>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server