Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/Frontend/src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ pre {
}

.btn-default {
padding: 8px 16px;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are legacy styles from the old site, and made it so that default and primary buttons were taller than other buttons in the same group

color: #333;
background-color: #fff;
border-color: #ccc;
Expand All @@ -266,7 +265,6 @@ pre {
}

.btn.btn-primary {
padding: 8px 16px;
background-color: var(--sp-blue);
border-color: #0686aa;
color: #fff;
Expand Down Expand Up @@ -416,7 +414,6 @@ span.metadata.metadata-link i.fa {

.btn.btn-retry-dismiss {
border-radius: 4px;
padding: 8px 16px;
}

.btn.btn-xs {
Expand Down
58 changes: 57 additions & 1 deletion src/Frontend/src/stores/LicenseDetailsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<EndpointSize[]>([]);
const endpoints = ref<Endpoint[]>([]);
Expand Down Expand Up @@ -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)) {
Expand All @@ -85,31 +99,41 @@ 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));
returnToUnassigned(queue);
}

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;
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -267,6 +322,7 @@ export const useLicenseDetailsStore = defineStore("LicenseDetailsStore", () => {
reclassifyEndpoint,
discardChanges,
saveChanges,
removeAmendments,
};
});

Expand Down
4 changes: 2 additions & 2 deletions src/Frontend/src/views/ThroughputReportView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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();
});
});
});
64 changes: 48 additions & 16 deletions src/Frontend/src/views/ThroughputReportView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>(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<string[]>([]);
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;
}
}
</script>
Expand All @@ -49,9 +66,24 @@ async function downloadReport() {
</div>
<div class="col-sm-6 text-end">
<span class="reason" v-if="!reportState?.report_can_be_generated">{{ reportState?.reason }}</span>
<button type="button" aria-label="Download Report" class="btn btn-primary actions" @click="generateReport()" :disabled="!reportState?.report_can_be_generated"><FAIcon :icon="faDownload" /> Download Report</button>
<ActionButton aria-label="Download Report" variant="primary" class="action" @click="generateReport()" :disabled="generating || !reportState?.report_can_be_generated" :icon="faDownload" :loading="generating">Download Report</ActionButton>
<Teleport to="#modalDisplay">
<ConfirmDialog v-if="showWarning" heading="Not all endpoints/queues have an Endpoint Type set" body="Are you sure you want to continue?" @cancel="showWarning = false" @confirm="downloadReport" />
<ModalDialog v-if="reportWarnings.length" id="download-report" title="Download Report" @close="reportWarnings = []" @confirm="downloadReport">
<template #modalBody>
<span>Please check the following:</span>
<ul>
<li v-for="warning in reportWarnings" :key="warning">
{{ warning }}
</li>
</ul>
<p>None of the above items are required, however providing as much information as possible about your current system as part of the usage report will assist in expediting your license renewal.</p>
<p>Are you sure you want to continue?</p>
</template>
<template #modalFooter>
<ActionButton variant="primary" aria-label="Yes" @click="downloadReport">Yes</ActionButton>
<ActionButton aria-label="No" @click="reportWarnings = []">No</ActionButton>
</template>
</ModalDialog>
</Teleport>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -53,7 +55,7 @@ watch(
<FAIcon :icon="faCircleChevronUp" class="collapse-button" size="lg" tabindex="0" title="collapse" @click="emit('collapse')" />
<strong v-tippy="endpoint.name">{{ endpoint.name }}</strong>
</div>
<div class="header-buttons">
<div class="header-buttons" v-if="isLicenseDetailAmendmentSupported">
<div>
<label :for="`${endpoint.clientId}classification`">Classification:</label>
<select :id="`${endpoint.clientId}classification`" @change="(e) => reclassifyEndpoint(Number((e.target as HTMLSelectElement).value))" :value="endpoint.classification" class="form-select">
Expand Down
Loading
Loading