diff --git a/src/Frontend/src/assets/main.css b/src/Frontend/src/assets/main.css index 8d749df20..35ec15b28 100644 --- a/src/Frontend/src/assets/main.css +++ b/src/Frontend/src/assets/main.css @@ -245,7 +245,6 @@ pre { } .btn-default { - padding: 8px 16px; color: #333; background-color: #fff; border-color: #ccc; @@ -266,7 +265,6 @@ pre { } .btn.btn-primary { - padding: 8px 16px; background-color: var(--sp-blue); border-color: #0686aa; color: #fff; @@ -416,7 +414,6 @@ span.metadata.metadata-link i.fa { .btn.btn-retry-dismiss { border-radius: 4px; - padding: 8px 16px; } .btn.btn-xs { diff --git a/src/Frontend/src/stores/LicenseDetailsStore.ts b/src/Frontend/src/stores/LicenseDetailsStore.ts index 561e5c055..780847707 100644 --- a/src/Frontend/src/stores/LicenseDetailsStore.ts +++ b/src/Frontend/src/stores/LicenseDetailsStore.ts @@ -7,9 +7,11 @@ import serviceControlClient from "@/components/serviceControlClient"; import useIsLicenseDetailsSupported from "@/views/throughputreport/licenseDetails/isLicenseDetailsSupported"; import { useShowToast } from "@/composables/toast"; import { TYPE } from "vue-toastification"; +import useIsLicenseDetailAmendmentSupported from "@/views/throughputreport/licenseDetails/isLicenseDetailAmendmentSupported"; export const useLicenseDetailsStore = defineStore("LicenseDetailsStore", () => { const isLicenseDetailsSupported = useIsLicenseDetailsSupported(); + const isLicenseDetailAmendmentSupported = useIsLicenseDetailAmendmentSupported(); const endpointSizes = ref([]); const endpoints = ref([]); @@ -42,36 +44,48 @@ export const useLicenseDetailsStore = defineStore("LicenseDetailsStore", () => { } function excludeQueue(queue: Queue) { + if (!isLicenseDetailAmendmentSupported) return; + excludedQueues.value.push(queue); unassignedQueues.value = unassignedQueues.value.filter((unassigned) => unassigned !== queue); hasChanges.value = true; } function unexcludeQueue(queue: Queue) { + if (!isLicenseDetailAmendmentSupported) return; + excludedQueues.value = excludedQueues.value.filter((assigned) => assigned !== queue); returnToUnassigned(queue); hasChanges.value = true; } function markQueueAsInfrastructure(queue: Queue) { + if (!isLicenseDetailAmendmentSupported) return; + infrastructureQueues.value.push(queue); unassignedQueues.value = unassignedQueues.value.filter((unassigned) => unassigned !== queue); hasChanges.value = true; } function unmarkQueueAsInfrastructure(queue: Queue) { + if (!isLicenseDetailAmendmentSupported) return; + infrastructureQueues.value = infrastructureQueues.value.filter((assigned) => assigned !== queue); returnToUnassigned(queue); hasChanges.value = true; } function returnToUnassigned(queue: Queue) { + if (!isLicenseDetailAmendmentSupported) return; + if (queue.details) unassignedQueues.value.push(queue); else unmappedQueues.value.push(queue); hasChanges.value = true; } function createEndpointFromQueue(queue: Queue) { + if (!isLicenseDetailAmendmentSupported) return; + //max endpoint name length of 80 - take from the end to reduce chance of clashes let name = queue.details.name.substring(queue.details.name.length - 80); while (endpoints.value.find((endpoint) => endpoint.name === name)) { @@ -85,12 +99,16 @@ export const useLicenseDetailsStore = defineStore("LicenseDetailsStore", () => { } function addQueueToEndpoint(queue: Queue, endpoint: Endpoint) { + if (!isLicenseDetailAmendmentSupported) return; + endpoint.queues.push(queue); unassignedQueues.value = unassignedQueues.value.filter((unassigned) => unassigned !== queue); hasChanges.value = true; } function removeQueueFromEndpoint(queue: Queue, endpoint: Endpoint) { + if (!isLicenseDetailAmendmentSupported) return; + endpoint.queues = endpoint.queues.filter((assigned) => assigned !== queue); //TODO //endpoint.excludeThroughput = endpoint.excludeThroughput.filter((et) => !queue.matches(et)); @@ -98,18 +116,24 @@ export const useLicenseDetailsStore = defineStore("LicenseDetailsStore", () => { } function removeEndpoint(endpoint: Endpoint) { + if (!isLicenseDetailAmendmentSupported) return; + endpoints.value = endpoints.value.filter((e) => e !== endpoint); endpoint.queues.forEach((queue) => returnToUnassigned(queue)); removedEndpoints.value.push(endpoint.clientId); } function renameEndpoint(endpoint: Endpoint, name: string) { + if (!isLicenseDetailAmendmentSupported) return; + endpoint.name = name; endpoint.amended = true; hasChanges.value = true; } function reclassifyEndpoint(endpoint: Endpoint, classification: EndpointClassification) { + if (!isLicenseDetailAmendmentSupported) return; + endpoint.classification = classification; endpoint.amended = true; hasChanges.value = true; @@ -194,6 +218,8 @@ export const useLicenseDetailsStore = defineStore("LicenseDetailsStore", () => { } async function saveChanges() { + if (!isLicenseDetailAmendmentSupported) return; + saving.value = true; function toApiQueue(queue: Queue) { return { name_hash: queue.nameHash, scope_hash: queue.scopeHash, original_location: queue.originalLocation }; @@ -222,13 +248,34 @@ export const useLicenseDetailsStore = defineStore("LicenseDetailsStore", () => { useShowToast(TYPE.SUCCESS, "Saved", "", false, { timeout: 1000 }); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { - useShowToast(TYPE.ERROR, "Error", "Failed to save changes to endpoint metadata."); + useShowToast(TYPE.ERROR, "Error", "Failed to save changes to endpoint metadata"); logger.error("Failed to save changes to endpoint metadata.", err); } finally { saving.value = false; } } + async function removeAmendments() { + if (!isLicenseDetailAmendmentSupported) return; + + saving.value = true; + try { + const response = await serviceControlClient.postToServiceControl("license/removeAmendments"); + if (!response.ok) { + const text = await response.text(); + throw new Error(`Remove Amendments Failed: ${response.status} ${response.statusText} - ${text}`); + } + await refresh(); + useShowToast(TYPE.SUCCESS, "Amendments Removed", "", false, { timeout: 1000 }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + useShowToast(TYPE.ERROR, "Error", "Failed to remove endpoint metadata amendments"); + logger.error("Failed to remove endpoint metadata amendments.", err); + } finally { + saving.value = false; + } + } + const compare = (q1: Queue, q2: Queue) => { const name1 = q1.details?.name; const name2 = q2.details?.name; @@ -252,6 +299,14 @@ export const useLicenseDetailsStore = defineStore("LicenseDetailsStore", () => { validId, hasLicenseDetails, hasChanges, + hasAmendments: computed( + () => + !hasChanges.value && + (endpoints.value.some((endpoint) => endpoint.queues.some((queue) => queue.originalLocation !== endpoint.clientId)) || + infrastructureQueues.value.some((queue) => queue.originalLocation !== "infrastructure") || + excludedQueues.value.some((queue) => queue.originalLocation !== "excluded") || + unassignedQueues.value.some((queue) => queue.originalLocation != null)) + ), saving, error, uploadEndpointMetadata, @@ -267,6 +322,7 @@ export const useLicenseDetailsStore = defineStore("LicenseDetailsStore", () => { reclassifyEndpoint, discardChanges, saveChanges, + removeAmendments, }; }); diff --git a/src/Frontend/src/views/ThroughputReportView.spec.ts b/src/Frontend/src/views/ThroughputReportView.spec.ts index 2f215b3bb..deb67e598 100644 --- a/src/Frontend/src/views/ThroughputReportView.spec.ts +++ b/src/Frontend/src/views/ThroughputReportView.spec.ts @@ -104,7 +104,7 @@ describe("EndpointsView tests", () => { const use = userEvent.setup(); await use.click(screen.getByRole("button", { name: /Download Report/i })); - expect(screen.queryAllByText(new RegExp(`Please email '${fileName}' to your account manager`)).length).toBeGreaterThanOrEqual(1); + expect(screen.queryAllByText(new RegExp(`Please email '${fileName}' to your Particular account manager.`)).length).toBeGreaterThanOrEqual(1); }); test("and there are warnings, dialog is displayed", async () => { @@ -116,7 +116,7 @@ describe("EndpointsView tests", () => { const use = userEvent.setup(); await use.click(screen.getByRole("button", { name: /Download Report/i })); - expect(screen.getByText("Not all endpoints/queues have an Endpoint Type set")).toBeInTheDocument(); + expect(screen.getByText("Not all endpoints/queues have an Endpoint Type set.")).toBeInTheDocument(); }); }); }); diff --git a/src/Frontend/src/views/ThroughputReportView.vue b/src/Frontend/src/views/ThroughputReportView.vue index da157a91d..e25512a36 100644 --- a/src/Frontend/src/views/ThroughputReportView.vue +++ b/src/Frontend/src/views/ThroughputReportView.vue @@ -5,36 +5,53 @@ import { useShowToast } from "@/composables/toast"; import { TYPE } from "vue-toastification"; import ServiceControlAvailable from "@/components/ServiceControlAvailable.vue"; import ThroughputSupported from "@/views/throughputreport/ThroughputSupported.vue"; -import ConfirmDialog from "@/components/ConfirmDialog.vue"; -import FAIcon from "@/components/FAIcon.vue"; import { faDownload } from "@fortawesome/free-solid-svg-icons"; import { computedAsync } from "@vueuse/core"; import useIsThroughputSupported from "./throughputreport/isThroughputSupported"; - -const showWarning = ref(false); +import ActionButton from "@/components/ActionButton.vue"; +import { useLicenseDetailsStore } from "@/stores/LicenseDetailsStore"; +import { storeToRefs } from "pinia"; +import ModalDialog from "@/components/ModalDialog.vue"; const isThroughputSupported = useIsThroughputSupported(); const throughputClient = createThroughputClient(); +const licenseDetailsStore = useLicenseDetailsStore(); +const { hasLicenseDetails, hasAmendments, unassignedQueues } = storeToRefs(licenseDetailsStore); const reportState = computedAsync(async () => (isThroughputSupported.value ? await throughputClient.reportAvailable() : null), null); +const reportWarnings = ref([]); +const generating = ref(false); async function generateReport() { - const results = await throughputClient.queues(); - const hasNonUserIndicatorEndpoint = results.find((value) => !value.user_indicator); + generating.value = true; + try { + const results = await throughputClient.queues(); + const hasNonUserIndicatorEndpoint = results.find((value) => !value.user_indicator); - if (hasNonUserIndicatorEndpoint) { - showWarning.value = true; - } else { - await downloadReport(); + if (hasNonUserIndicatorEndpoint) reportWarnings.value.push("Not all endpoints/queues have an Endpoint Type set."); + if (hasLicenseDetails.value && unassignedQueues.value.length > 0) { + if (hasAmendments.value) reportWarnings.value.push("Amendments have been made to the license details, but some queues remain unassigned."); + else reportWarnings.value.push("Some queues remain unassigned on the license details tab."); + } + if (!reportWarnings.value.length) { + await downloadReport(); + } + } finally { + generating.value = false; } } async function downloadReport() { - showWarning.value = false; - const fileName = await throughputClient.downloadReport(); + generating.value = true; + reportWarnings.value = []; + try { + const fileName = await throughputClient.downloadReport(); - if (fileName !== "") { - useShowToast(TYPE.INFO, "Report Downloaded", `Please email '${fileName}' to your account manager.`, true); + if (fileName !== "") { + useShowToast(TYPE.INFO, "Report Downloaded", `Please email '${fileName}' to your Particular account manager.`, true); + } + } finally { + generating.value = false; } } @@ -49,9 +66,24 @@ async function downloadReport() {
{{ reportState?.reason }} - + Download Report - + + + +
diff --git a/src/Frontend/src/views/throughputreport/licenseDetails/EndpointHeader.vue b/src/Frontend/src/views/throughputreport/licenseDetails/EndpointHeader.vue index 248dabfa8..ea9b22a62 100644 --- a/src/Frontend/src/views/throughputreport/licenseDetails/EndpointHeader.vue +++ b/src/Frontend/src/views/throughputreport/licenseDetails/EndpointHeader.vue @@ -7,11 +7,13 @@ import EndpointName from "./EndpointName.vue"; import ActionButton from "@/components/ActionButton.vue"; import { useLicenseDetailsStore } from "@/stores/LicenseDetailsStore"; import { faCircleChevronUp } from "@fortawesome/free-solid-svg-icons"; +import useIsLicenseDetailAmendmentSupported from "./isLicenseDetailAmendmentSupported.ts"; const props = defineProps<{ endpoint: Endpoint }>(); const emit = defineEmits(["collapse"]); const licenseDetailsStore = useLicenseDetailsStore(); +const isLicenseDetailAmendmentSupported = useIsLicenseDetailAmendmentSupported(); const endpointClassificationStyle = computed(() => { switch (props.endpoint.classification) { @@ -53,7 +55,7 @@ watch( {{ endpoint.name }} -
+