Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { onBeforeRouteLeave, useRoute } from "vue-router";
import { useCookies } from "vue3-cookies";
import LicenseExpired from "../../components/LicenseExpired.vue";
import ServiceControlNotAvailable from "../ServiceControlNotAvailable.vue";
import MessageList from "./MessageList.vue";
import MessageList, { IMessageList } from "./MessageList.vue";
import ConfirmDialog from "../ConfirmDialog.vue";
import PaginationStrip from "../../components/PaginationStrip.vue";
import moment from "moment";
Expand All @@ -18,7 +18,7 @@ import { TYPE } from "vue-toastification";
import FailureGroup from "@/resources/FailureGroup";

let pollingFaster = false;
let refreshInterval: ReturnType<typeof setInterval>;
let refreshInterval: number | undefined;
const perPage = 50;
const configuration = ref<Configuration | null>(null);

Expand All @@ -30,7 +30,7 @@ const totalCount = ref(0);
const cookies = useCookies().cookies;
const selectedPeriod = ref("Deleted in the last 7 days");
const showConfirmRestore = ref(false);
const messageList = ref();
const messageList = ref<IMessageList | undefined>();
const messages = ref<ExtendedFailedMessage[]>([]);
const periodOptions = ["All Deleted", "Deleted in the last 2 Hours", "Deleted in the last 1 Day", "Deleted in the last 7 days"];

Expand Down Expand Up @@ -126,32 +126,32 @@ function updateMessagesScheduledDeletionDate(messages: ExtendedFailedMessage[])
}

function numberSelected() {
return messageList?.value?.getSelectedMessages()?.length ?? 0;
return messageList.value?.getSelectedMessages()?.length ?? 0;
}

function selectAll() {
messageList.value.selectAll();
messageList.value?.selectAll();
}

function deselectAll() {
messageList.value.deselectAll();
messageList.value?.deselectAll();
}

function isAnythingSelected() {
return messageList?.value?.isAnythingSelected();
return messageList.value?.isAnythingSelected();
}

async function restoreSelectedMessages() {
changeRefreshInterval(1000);
const selectedMessages = messageList.value.getSelectedMessages() as ExtendedFailedMessage[]; //TODO: remove this cast once messageList has been converted to typescript
const selectedMessages = messageList.value?.getSelectedMessages() ?? [];
selectedMessages.forEach((m) => (m.restoreInProgress = true));
useShowToast(TYPE.INFO, "Info", `restoring ${selectedMessages.length} messages...`);

await usePatchToServiceControl(
"errors/unarchive",
selectedMessages.map((m) => m.id)
);
messageList.value.deselectAll();
messageList.value?.deselectAll();
}

function periodChanged(period: string) {
Expand All @@ -171,11 +171,11 @@ function isRestoreInProgress() {
}

function changeRefreshInterval(milliseconds: number) {
if (refreshInterval) {
clearInterval(refreshInterval);
if (refreshInterval != null) {
window.clearInterval(refreshInterval);
}

refreshInterval = setInterval(() => {
refreshInterval = window.setInterval(() => {
// If we're currently polling at 5 seconds and there is a restore in progress, then change the polling interval to poll every 1 second
if (!pollingFaster && isRestoreInProgress()) {
changeRefreshInterval(1000);
Expand All @@ -200,8 +200,8 @@ onBeforeMount(() => {
});

onUnmounted(() => {
if (refreshInterval) {
clearInterval(refreshInterval);
if (refreshInterval != null) {
window.clearInterval(refreshInterval);
}
});

Expand Down Expand Up @@ -256,7 +256,7 @@ onMounted(() => {
</div>
<div class="row">
<div class="col-12">
<MessageList :messages="messages" :showRequestRetry="false" ref="messageList"></MessageList>
<MessageList :messages="messages" ref="messageList"></MessageList>
</div>
</div>
<div class="row" v-if="messages.length > 0">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script setup>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from "vue";
import { licenseStatus } from "../../composables/serviceLicense";
import { connectionState } from "../../composables/serviceServiceControl";
import { useFetchFromServiceControl, usePatchToServiceControl } from "../../composables/serviceServiceControlUrls";
import { useFetchFromServiceControl, usePatchToServiceControl, useTypedFetchFromServiceControl } from "../../composables/serviceServiceControlUrls";
import { useShowToast } from "../../composables/toast";
import { useRetryMessages } from "../../composables/serviceFailedMessage";
import { useDownloadFile } from "../../composables/fileDownloadCreator";
Expand All @@ -11,63 +11,58 @@ import { useArchiveExceptionGroup, useRetryExceptionGroup } from "../../composab
import LicenseExpired from "../../components/LicenseExpired.vue";
import OrderBy from "./OrderBy.vue";
import ServiceControlNotAvailable from "../ServiceControlNotAvailable.vue";
import MessageList from "./MessageList.vue";
import MessageList, { IMessageList } from "./MessageList.vue";
import ConfirmDialog from "../ConfirmDialog.vue";
import PaginationStrip from "../../components/PaginationStrip.vue";
import { FailedMessageStatus } from "@/resources/FailedMessage";
import { ExtendedFailedMessage, FailedMessageStatus } from "@/resources/FailedMessage";
import SortOptions, { SortDirection } from "@/resources/SortOptions";
import { TYPE } from "vue-toastification";

let pollingFaster = false;
let refreshInterval = undefined;
let sortMethod = undefined;
let refreshInterval: number | undefined;
let sortMethod: SortOptions | undefined;
const perPage = 50;
const route = useRoute();
const groupId = ref(route.params.groupId);
const groupId = ref<string>(route.params.groupId as string);
const groupName = ref("");
const pageNumber = ref(1);
const totalCount = ref(0);
const showDelete = ref(false);
const showConfirmRetryAll = ref(false);
const showConfirmDeleteAll = ref(false);
const messageList = ref();
const messages = ref([]);
const sortOptions = [
const messageList = ref<IMessageList>();
const messages = ref<ExtendedFailedMessage[]>([]);
const sortOptions: SortOptions[] = [
{
description: "Time of failure",
selector: function (group) {
return group.title;
},
icon: "bi-sort-",
},
{
description: "Message Type",
selector: function (group) {
return group.count;
},
icon: "bi-sort-alpha-",
},
];

watch(pageNumber, () => loadMessages());

function sortGroups(sort) {
function sortGroups(sort: SortOptions) {
sortMethod = sort;
loadMessages();
}

function loadMessages() {
loadPagedMessages(groupId.value, pageNumber.value, sortMethod?.description.replaceAll(" ", "_").toLowerCase(), sortMethod.dir);
loadPagedMessages(groupId.value, pageNumber.value, sortMethod && sortMethod.description.replaceAll(" ", "_").toLowerCase(), sortMethod?.dir);
}

async function loadGroupDetails(groupId) {
async function loadGroupDetails(groupId: string) {
const response = await useFetchFromServiceControl(`recoverability/groups/id/${groupId}`);
const data = await response.json();
groupName.value = data.title;
}

function loadPagedMessages(groupId, page, sortBy, direction) {
if (typeof sortBy === "undefined") sortBy = "time_of_failure";
if (typeof direction === "undefined") direction = "desc";
if (typeof page === "undefined") page = 1;
function loadPagedMessages(groupId: string, page: number, sortBy?: string, direction?: SortDirection) {
sortBy ??= "time_of_failure";
direction ??= SortDirection.Descending;

let loadGroupDetailsPromise;
if (groupId && !groupName.value) {
Expand All @@ -76,9 +71,10 @@ function loadPagedMessages(groupId, page, sortBy, direction) {

async function loadMessages() {
try {
const response = await useFetchFromServiceControl(`${groupId ? `recoverability/groups/${groupId}/` : ""}errors?status=${FailedMessageStatus.Unresolved}&page=${page}&per_page=${perPage}&sort=${sortBy}&direction=${direction}`);
totalCount.value = parseInt(response.headers.get("Total-Count"));
const data = await response.json();
const [response, data] = await useTypedFetchFromServiceControl<ExtendedFailedMessage[]>(
`${groupId ? `recoverability/groups/${groupId}/` : ""}errors?status=${FailedMessageStatus.Unresolved}&page=${page}&per_page=${perPage}&sort=${sortBy}&direction=${direction}`
);
totalCount.value = parseInt(response.headers.get("Total-Count") ?? "");
if (messages.value.length && data.length) {
// merge the previously selected messages into the new list so we can replace them
messages.value.forEach((previousMessage) => {
Expand Down Expand Up @@ -112,9 +108,9 @@ function loadPagedMessages(groupId, page, sortBy, direction) {
return loadMessagesPromise;
}

async function retryRequested(id) {
async function retryRequested(id: string) {
changeRefreshInterval(1000);
useShowToast("info", "Info", "Message retry requested...");
useShowToast(TYPE.INFO, "Info", "Message retry requested...");
await useRetryMessages([id]);
const message = messages.value.find((m) => m.id === id);
if (message) {
Expand All @@ -125,15 +121,18 @@ async function retryRequested(id) {

async function retrySelected() {
changeRefreshInterval(1000);
const selectedMessages = messageList.value.getSelectedMessages();
useShowToast("info", "Info", "Retrying " + selectedMessages.length + " messages...");
const selectedMessages = messageList.value?.getSelectedMessages() ?? [];
useShowToast(TYPE.INFO, "Info", "Retrying " + selectedMessages.length + " messages...");
await useRetryMessages(selectedMessages.map((m) => m.id));
messageList.value.deselectAll();
messageList.value?.deselectAll();
selectedMessages.forEach((m) => (m.retryInProgress = true));
}

//TODO: this function doesn't work correctly, since any commas in the exception trace breaks the CSV.
//Not attempting to use explicit types correctly since this will need to change eventually anyway
function exportSelected() {
function toCSV(array) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function toCSV(array: any[]) {
const keys = Object.keys(array[0]);
let result = keys.join("\t") + "\n";
array.forEach((obj) => {
Expand All @@ -143,17 +142,19 @@ function exportSelected() {
return result;
}

function parseObject(obj, propertiesToSkip, path = "") {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function parseObject(obj: any | null, propertiesToSkip: string[], path = "") {
const type = typeof obj;
let d = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let d = {} as any;

if (type === "array" || type === "object") {
if (obj != null && type === "object") {
for (const i in obj) {
const newD = parseObject(obj[i], propertiesToSkip, path + i + ".");
d = Object.assign(d, newD);
}
return d;
} else if (type === "number" || type === "string" || type === "boolean" || type === "null") {
} else if (type === "number" || type === "string" || type === "boolean" || obj == null) {
const endPath = path.substr(0, path.length - 1);
if (propertiesToSkip && propertiesToSkip.includes(endPath)) {
return d;
Expand All @@ -165,7 +166,7 @@ function exportSelected() {
return d;
}

const selectedMessages = messageList.value.getSelectedMessages();
const selectedMessages = messageList.value?.getSelectedMessages() ?? [];
const propertiesToSkip = ["hover", "selected", "hover2", "$$hashKey", "panel", "edit_of", "edited"];

const preparedMessagesForExport = [];
Expand All @@ -178,15 +179,15 @@ function exportSelected() {
}

function numberSelected() {
return messageList?.value?.getSelectedMessages()?.length ?? 0;
return messageList.value?.getSelectedMessages()?.length ?? 0;
}

function selectAll() {
messageList.value.selectAll();
messageList.value?.selectAll();
}

function deselectAll() {
messageList.value.deselectAll();
messageList.value?.deselectAll();
}

function isAnythingSelected() {
Expand All @@ -195,25 +196,25 @@ function isAnythingSelected() {

async function deleteSelectedMessages() {
changeRefreshInterval(1000);
const selectedMessages = messageList.value.getSelectedMessages();
const selectedMessages = messageList.value?.getSelectedMessages() ?? [];

useShowToast("info", "Info", "Deleting " + selectedMessages.length + " messages...");
useShowToast(TYPE.INFO, "Info", "Deleting " + selectedMessages.length + " messages...");
await usePatchToServiceControl(
"errors/archive",
selectedMessages.map((m) => m.id)
);
messageList.value.deselectAll();
messageList.value?.deselectAll();
selectedMessages.forEach((m) => (m.deleteInProgress = true));
}

async function retryGroup() {
useShowToast("info", "Info", "Retrying all messages...");
useShowToast(TYPE.INFO, "Info", "Retrying all messages...");
await useRetryExceptionGroup(groupId.value);
messages.value.forEach((m) => (m.retryInProgress = true));
}

async function deleteGroup() {
useShowToast("info", "Info", "Deleting all messages...");
useShowToast(TYPE.INFO, "Info", "Deleting all messages...");
await useArchiveExceptionGroup(groupId.value);
messages.value.forEach((m) => (m.deleteInProgress = true));
}
Expand All @@ -224,12 +225,12 @@ function isRetryOrDeleteOperationInProgress() {
});
}

function changeRefreshInterval(milliseconds) {
if (typeof refreshInterval !== "undefined") {
clearInterval(refreshInterval);
function changeRefreshInterval(milliseconds: number) {
if (refreshInterval != null) {
window.clearInterval(refreshInterval);
}

refreshInterval = setInterval(() => {
refreshInterval = window.setInterval(() => {
// If we're currently polling at 5 seconds and there is a retry or delete in progress, then change the polling interval to poll every 1 second
if (!pollingFaster && isRetryOrDeleteOperationInProgress()) {
changeRefreshInterval(1000);
Expand All @@ -245,13 +246,13 @@ function changeRefreshInterval(milliseconds) {
}

onBeforeRouteLeave(() => {
groupId.value = undefined;
groupName.value = undefined;
groupId.value = "";
groupName.value = "";
});

onUnmounted(() => {
if (typeof refreshInterval !== "undefined") {
clearInterval(refreshInterval);
if (refreshInterval != null) {
window.clearInterval(refreshInterval);
}
});

Expand Down Expand Up @@ -294,7 +295,7 @@ onMounted(() => {
</div>
<div class="row">
<div class="col-12">
<MessageList :messages="messages" :showRequestRetry="true" @retry-requested="retryRequested" ref="messageList"></MessageList>
<MessageList :messages="messages" :show-request-retry="true" @retry-requested="retryRequested" ref="messageList"></MessageList>
</div>
</div>
<div class="row">
Expand Down
Loading