Skip to main content
Glama
ChangeSetPanel.vue9.05 kB
<template> <div class="flex flex-row gap-xs items-end flex-1 min-w-[172px] max-w-fit"> <label class="flex flex-col flex-1 min-w-0 max-w-fit"> <div class="text-[11px] mt-[1px] mb-[5px] capsize font-medium text-neutral-300 whitespace-nowrap" > CHANGE SET: </div> <DropdownMenuButton ref="dropdownMenuRef" v-model="selectedChangeSetId" :options="changeSetSearchFilteredOptions" :search=" changeSetDropdownOptions.length > DEFAULT_DROPDOWN_SEARCH_THRESHOLD " placeholder="-- select a change set --" checkable variant="navbar" :enableSecondaryAction="calculateShowSecondaryAction" :sizeClass="tw`h-[28px]`" secondaryActionIcon="edit2" @select="onSelectChangeSet" @secondaryAction="openRenameModal" > <template #afterOptions> <DropdownMenuItem label="Create New Change Set" icon="plus" disableCheckable @select=" () => { onSelectChangeSet('NEW'); } " /> </template> <DropdownMenuItem v-if="changeSetSearchFilteredOptions.length === 0" label="No Change Sets Match Your Search" header /> </DropdownMenuButton> </label> <NewButton v-tooltip="{ content: 'Create Change Set', }" data-testid="create-change-set-button" icon="git-branch-plus" tone="action" class="flex-none" @click="openCreateModal" /> <NewButton v-tooltip="{ content: 'Abandon Change Set', }" data-testid="abandon-change-set-button" :disabled=" !selectedChangeSetName || changeSetsStore.headSelected || changeSetsStore.creatingChangeSet || changeSetsStore.selectedChangeSet?.status === ChangeSetStatus.NeedsApproval " icon="trash" tone="destructive" class="flex-none" @click="openAbandonConfirmationModal" /> <Modal ref="createModalRef" title="Create Change Set"> <form @submit.prevent="onCreateChangeSet"> <Stack> <p> Modeling a configuration or extending SI happens within <b>Change Sets</b>. Think of these like light-weight branches, allowing you to experiment freely without risk of impacting production systems. </p> <p> Please give your <b>Change Set</b> a name below, and click the Create button. </p> <VormInput v-model="createChangeSetName" :regex="CHANGE_SET_NAME_REGEX" label="Change set name" regexMessage="You cannot name a change set 'HEAD' - please choose another name." required requiredMessage="Please choose a name for your change set!" @enterPressed="onCreateChangeSet" /> <div class="flex flex-row-reverse gap-sm"> <VButton :disabled="validationState.isError" :requestStatus="createChangeSetReqStatus" class="flex-grow" icon="plus-circle" label="Create change set" loadingText="Creating Change Set" submit tone="success" /> </div> </Stack> </form> </Modal> <AbandonChangeSetModal ref="abandonModalRef" /> <ChangesetRenameModal v-if="enableChangesetRename" ref="renameModalRef" /> </div> </template> <script lang="ts" setup> import { computed, onMounted, ref, watch } from "vue"; import * as _ from "lodash-es"; import { useRoute, useRouter } from "vue-router"; import { VButton, VormInput, Stack, Modal, useValidatedInputGroup, DropdownMenuButton, DropdownMenuItem, DEFAULT_DROPDOWN_SEARCH_THRESHOLD, NewButton, } from "@si/vue-lib/design-system"; import { tw } from "@si/vue-lib"; import { useChangeSetsStore } from "@/store/change_sets.store"; import { ChangeSetStatus } from "@/api/sdf/dal/change_set"; import AbandonChangeSetModal from "@/components/AbandonChangeSetModal.vue"; import { reset } from "@/newhotness/logic_composables/navigation_stack"; import ChangesetRenameModal from "@/components/ChangesetRenameModal.vue"; import * as heimdall from "../../../store/realtime/heimdall"; const CHANGE_SET_NAME_REGEX = /^(?!head).*$/i; const changeSetsStore = useChangeSetsStore(); const openChangeSets = computed(() => changeSetsStore.openChangeSets); const selectedChangeSetId = computed(() => changeSetsStore.selectedChangeSetId); const selectedChangeSetName = computed( () => changeSetsStore.selectedChangeSet?.name, ); const dropdownMenuRef = ref<InstanceType<typeof DropdownMenuButton>>(); const changeSetDropdownOptions = computed(() => [ ..._.map(openChangeSets.value, (cs) => ({ value: cs.id, label: cs.name, })), // { value: "NEW", label: "+ Create new change set" }, ]); const changeSetSearchFilteredOptions = computed(() => { const searchString = dropdownMenuRef.value?.searchString; if (!searchString || searchString === "") { return changeSetDropdownOptions.value; } return changeSetDropdownOptions.value.filter( (option) => option.label.toLocaleLowerCase().includes(searchString) || option.value.toLocaleLowerCase().includes(searchString), ); }); const router = useRouter(); const route = useRoute(); // NOTE(victor): The changeset modal uses the new api lib, so we're not enabling the rename on the old ui // TODO: Remove this when the old ui is deprecated const enableChangesetRename = computed(() => route.name?.toString().startsWith("new-hotness"), ); const calculateShowSecondaryAction = (option: { label: string }) => { return enableChangesetRename.value && option.label.toUpperCase() !== "HEAD"; }; const abandonModalRef = ref<InstanceType<typeof AbandonChangeSetModal> | null>( null, ); const openAbandonConfirmationModal = () => { abandonModalRef.value?.open(); }; const renameModalRef = ref<InstanceType<typeof ChangesetRenameModal> | null>( null, ); function openRenameModal(option: { value: string; label: string }) { renameModalRef.value?.open(option.value, option.label); } const createModalRef = ref<InstanceType<typeof Modal>>(); const changeSetsReqStatus = changeSetsStore.getRequestStatus("FETCH_CHANGE_SETS"); const checkFirstLoad = () => { if (!changeSetsReqStatus.value.isSuccess || !createModalRef.value) return; const isFirstLoad = !window.localStorage.getItem("ran-first-load"); window.localStorage.setItem("ran-first-load", "true"); if (isFirstLoad) { createModalRef.value?.open(); } }; watch([changeSetsReqStatus], checkFirstLoad); onMounted(() => { checkFirstLoad(); }); // We don't add a change set name const createChangeSetName = ref(""); const { validationState, validationMethods } = useValidatedInputGroup(); const waitForChangeSetExists = ( workspaceId: string, changeSetId: string, ): Promise<void> => { const INTERVAL_MS = 50; const MAX_WAIT_IN_SEC = 10; const MAX_RETRIES = (MAX_WAIT_IN_SEC * 1000) / INTERVAL_MS; return new Promise((resolve, reject) => { let retry = 0; const interval = setInterval(async () => { if (retry >= MAX_RETRIES) { clearInterval(interval); reject(); } if (await heimdall.changeSetExists(workspaceId, changeSetId)) { clearInterval(interval); resolve(); } retry += 1; }, INTERVAL_MS); }); }; async function onSelectChangeSet(newVal: string) { if (newVal === "NEW") { createModalRef.value?.open(); return; } if (newVal && route.name) { // keep everything in the current route except the change set id // note - we use push here, so there is a new browser history entry if ( route.name?.toString().startsWith("new-hotness") && typeof route.params.workspacePk === "string" ) { const workspaceId = route.params.workspacePk; await waitForChangeSetExists(workspaceId, newVal); } const name = route.name; await router.push({ name, params: { ...route.params, changeSetId: newVal, }, query: route.query, }); reset(); } } async function onCreateChangeSet() { if (validationMethods.hasError()) return; const createReq = await changeSetsStore.CREATE_CHANGE_SET( createChangeSetName.value, ); if (createReq.result.success && createReq.result.data?.id) { const newChangeSetId = createReq.result.data.id; onSelectChangeSet(newChangeSetId); createModalRef.value?.close(); } } const createChangeSetReqStatus = changeSetsStore.getRequestStatus("CREATE_CHANGE_SET"); function openCreateModal() { if (createModalRef.value?.isOpen) return; // Reset the name on new open of the modal createChangeSetName.value = ""; createModalRef.value?.open(); } defineExpose({ openCreateModal }); </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