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
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
} from "@features/support/utils/support";
import { formatBackendTimestampForDisplay } from "@utils/dateTime";
import { getSeverityLegendColor } from "@features/dashboard/utils/dashboard";
import { formatCasesTableCaseIdentifier } from "@features/dashboard/utils/casesTable";
import ErrorIndicator from "@components/error-indicator/ErrorIndicator";
import CasesTableSkeleton from "@features/dashboard/components/cases-table/CasesTableSkeleton";
import EmptyIcon from "@components/empty-state/EmptyIcon";
Expand Down Expand Up @@ -209,7 +210,10 @@ const CasesList = ({
variant="caption"
color="text.secondary"
>
ID: {row.number}
{formatCasesTableCaseIdentifier(
row.number,
row.internalId,
)}
</Typography>
</Box>
</TableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ describe("CasesList", () => {
createdOn: "2024-01-01 10:00:00",
title: "Test Case 1",
number: "CS-001",
internalId: "INT-001",
assignedEngineer: "John Doe",
createdBy: "Jane Smith",
severity: { id: 1, label: "High" },
Expand Down Expand Up @@ -198,7 +199,7 @@ describe("CasesList", () => {
expect(screen.getByText("Created by")).toBeInTheDocument();
expect(screen.getByText("Jane Smith")).toBeInTheDocument();
expect(screen.getByText("Test Case 1")).toBeInTheDocument();
expect(screen.getByText("ID: CS-001")).toBeInTheDocument();
expect(screen.getByText("ID: CS-001 | INT-001")).toBeInTheDocument();
expect(screen.getByText("Test Case 2")).toBeInTheDocument();
expect(screen.getByText("ID: CS-002")).toBeInTheDocument();
expect(screen.getAllByTestId("table-row")).toHaveLength(3); // 1 header + 2 data rows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
*/

import { describe, expect, it } from "vitest";
import { getStatusColor, getSeverityColor } from "@features/dashboard/utils/casesTable";
import {
formatCasesTableCaseIdentifier,
getSeverityColor,
getStatusColor,
} from "@features/dashboard/utils/casesTable";
import { CaseSeverityLevel, CaseStatus } from "@features/support/constants/supportConstants";

describe("casesTable utils", () => {
Expand Down Expand Up @@ -55,4 +59,30 @@ describe("casesTable utils", () => {
expect(getStatusColor("Unknown")).toBe("text.secondary");
});
});

describe("formatCasesTableCaseIdentifier", () => {
it("should join number and internal id with a pipe after ID prefix", () => {
expect(formatCasesTableCaseIdentifier("CS-001", "INT-1")).toBe(
"ID: CS-001 | INT-1",
);
});

it("should return ID and number when internal id is missing", () => {
expect(formatCasesTableCaseIdentifier("CS-001", undefined)).toBe(
"ID: CS-001",
);
});

it("should return ID and internal id when number is missing", () => {
expect(formatCasesTableCaseIdentifier(undefined, "INT-1")).toBe(
"ID: INT-1",
);
});

it("should return ID placeholder when both are missing", () => {
expect(formatCasesTableCaseIdentifier(undefined, undefined)).toBe(
"ID: --",
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,30 @@ export function navigateToProjectCaseDetail(
navigate(`/projects/${projectId}/support/cases/${caseId}`);
}

/**
* Formats case number and WSO2 internal id for the dashboard cases table details row.
*
* @param number - Portal case number.
* @param internalId - WSO2 case internal id.
* @returns Display string such as `ID: CS-001 | INT-1`, or `ID: --` when both are missing.
*/
export function formatCasesTableCaseIdentifier(
number?: string | null,
internalId?: string | null,
): string {
const caseNumber = number?.trim() ?? "";
const wso2CaseId = internalId?.trim() ?? "";
let identifier = "--";
if (caseNumber && wso2CaseId) {
identifier = `${caseNumber} | ${wso2CaseId}`;
} else if (caseNumber) {
identifier = caseNumber;
} else if (wso2CaseId) {
identifier = wso2CaseId;
}
return `ID: ${identifier}`;
}

/**
* Maps TabBar tab id to dashboard cases view mode.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import {
import type { CaseMetadataResponse } from "@features/support/types/cases";
import type { ChangeRequestFilterValues } from "@features/operations/types/changeRequests";
import type { SupportStatConfig } from "@features/support/constants/supportConstants";
import { ChangeRequestsViewMode } from "@features/operations/types/changeRequests";
import {
ChangeRequestSortField,
ChangeRequestsViewMode,
} from "@features/operations/types/changeRequests";
import { ServiceRequestCaseSortField } from "@features/operations/types/serviceRequests";

/**
Expand Down Expand Up @@ -236,6 +239,27 @@ export const CHANGE_REQUESTS_SEARCH_PLACEHOLDER =

export const CHANGE_REQUESTS_ENTITY_LABEL = "change requests";

export type ChangeRequestSortFieldOption = {
value: ChangeRequestSortField;
label: string;
kind?: "chronological" | "ordinal";
};

/** Sort options for change requests list (updated / created only). */
export const CHANGE_REQUESTS_SORT_FIELD_OPTIONS: ChangeRequestSortFieldOption[] =
[
{
value: ChangeRequestSortField.UpdatedOn,
label: "Updated on",
kind: "chronological",
},
{
value: ChangeRequestSortField.CreatedOn,
label: "Created on",
kind: "chronological",
},
];

export const CHANGE_REQUESTS_VIEW_TAB_LIST_LABEL = "List View";

export const CHANGE_REQUESTS_VIEW_TAB_CALENDAR_LABEL = "Calendar View";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ import ChangeRequestsList from "@features/operations/components/change-requests/
import ChangeRequestsCalendarView from "@features/operations/components/change-requests/ChangeRequestsCalendarView";
import TabBar from "@components/tab-bar/TabBar";
import { generateChangeRequestsSchedulePdf } from "@features/operations/utils/changeRequestsSchedulePdf";
import { SortOrder } from "@/types/common";
import { hasListSearchOrFilters, countListSearchAndFilters } from "@features/support/utils/support";
import {
ChangeRequestFilterDefinitionId,
ChangeRequestSortField,
ChangeRequestsViewMode,
} from "@features/operations/types/changeRequests";
import {
Expand All @@ -61,6 +63,7 @@ import {
CHANGE_REQUESTS_PAGE_TITLE_OUTSTANDING,
CHANGE_REQUESTS_PAGE_TITLE_SCHEDULED,
CHANGE_REQUESTS_SEARCH_PLACEHOLDER,
CHANGE_REQUESTS_SORT_FIELD_OPTIONS,
CHANGE_REQUESTS_VIEW_TABS_CONFIG,
OPERATIONS_LIST_BACK_LABEL,
OPERATIONS_LIST_PAGE_SIZE,
Expand Down Expand Up @@ -104,6 +107,18 @@ export default function ChangeRequestsPage(): JSX.Element {
);
const [page, setPage] = useSessionState<number>(`${sessionPrefix}-page`, 1, undefined, { popOnly: true });
const [rowsPerPage, setRowsPerPage] = useSessionState<number>(`${sessionPrefix}-rowsPerPage`, OPERATIONS_LIST_PAGE_SIZE, undefined, { popOnly: true });
const [sortField, setSortField] = useSessionState<ChangeRequestSortField>(
`${sessionPrefix}-sortField`,
ChangeRequestSortField.UpdatedOn,
undefined,
{ popOnly: true },
);
const [sortOrder, setSortOrder] = useSessionState<SortOrder>(
`${sessionPrefix}-sortOrder`,
SortOrder.DESC,
undefined,
{ popOnly: true },
);
const [isExporting, setIsExporting] = useState(false);

const { data: filterMetadata } = useGetProjectFilters(projectId || "");
Expand All @@ -119,8 +134,26 @@ export default function ChangeRequestsPage(): JSX.Element {
const isPresetMode = outstandingOnly || actionRequired || scheduledOnly;
const effectiveFilters = isPresetMode ? {} : filters;
const effectiveSearchTerm = isPresetMode ? "" : searchTerm;
return buildChangeRequestSearchRequest(effectiveFilters, effectiveSearchTerm, outstandingOnly, actionRequired, scheduledOnly, filterMetadata?.changeRequestStates);
}, [searchTerm, filters, outstandingOnly, actionRequired, scheduledOnly, filterMetadata?.changeRequestStates]);
return buildChangeRequestSearchRequest(
effectiveFilters,
effectiveSearchTerm,
outstandingOnly,
actionRequired,
scheduledOnly,
filterMetadata?.changeRequestStates,
sortField,
sortOrder,
);
}, [
searchTerm,
filters,
outstandingOnly,
actionRequired,
scheduledOnly,
filterMetadata?.changeRequestStates,
sortField,
sortOrder,
]);

const offset = (page - 1) * rowsPerPage;

Expand Down Expand Up @@ -249,6 +282,16 @@ export default function ChangeRequestsPage(): JSX.Element {
setPage(1);
};

const handleSortChange = (value: SortOrder) => {
setSortOrder(value);
setPage(1);
};

const handleSortFieldChange = (value: string) => {
setSortField(value as ChangeRequestSortField);
setPage(1);
};

const handleChangeRequestClick = (item: ChangeRequestItem): void => {
navigate(
`/projects/${projectId}/${navSegment}/change-requests/${item.id}`,
Expand Down Expand Up @@ -376,6 +419,11 @@ export default function ChangeRequestsPage(): JSX.Element {
shownCount={changeRequests.length}
totalCount={totalRecords}
entityLabel={CHANGE_REQUESTS_ENTITY_LABEL}
sortFieldOptions={CHANGE_REQUESTS_SORT_FIELD_OPTIONS}
sortField={sortField}
onSortFieldChange={handleSortFieldChange}
sortOrder={sortOrder}
onSortOrderChange={handleSortChange}
rightContent={
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
{hideSearchPanel ? downloadResultsButton : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ import {
resolveOutstandingCrStateIds,
resolveScheduledCrStateIds,
} from "@features/operations/utils/operationsPages";
import { ChangeRequestFilterDefinitionId } from "@features/operations/types/changeRequests";
import {
ChangeRequestFilterDefinitionId,
ChangeRequestSortField,
} from "@features/operations/types/changeRequests";
import {
OperationsNavSegment,
ServiceRequestCaseSortField,
Expand Down Expand Up @@ -197,11 +200,26 @@ describe("buildChangeRequestSearchRequest", () => {
expect(req.filters?.stateKeys).not.toContain(-3);
});

it("sorts by updatedOn descending", () => {
it("sorts by updatedOn descending by default", () => {
const req = buildChangeRequestSearchRequest({}, "", false, false, false, allStates);
expect(req.sortBy?.field).toBe("updatedOn");
expect(req.sortBy?.order).toBe("desc");
});

it("applies custom sort field and order", () => {
const req = buildChangeRequestSearchRequest(
{},
"",
false,
false,
false,
allStates,
ChangeRequestSortField.CreatedOn,
SortOrder.ASC,
);
expect(req.sortBy?.field).toBe("createdOn");
expect(req.sortBy?.order).toBe("asc");
});
});

describe("resolveChangeRequestFilterListOptions", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ export function resolveClosedCrStateIds(
* @param actionRequired - Restrict to action-required states.
* @param scheduledOnly - Restrict to scheduled state.
* @param changeRequestStates - Raw states metadata from `useGetProjectFilters`.
* @param sortField - Active sort field.
* @param sortOrder - Sort direction.
* @returns Request body without pagination.
*/
export function buildChangeRequestSearchRequest(
Expand All @@ -201,6 +203,8 @@ export function buildChangeRequestSearchRequest(
actionRequired: boolean = false,
scheduledOnly: boolean = false,
changeRequestStates?: MetadataItem[],
sortField: ChangeRequestSortField = ChangeRequestSortField.UpdatedOn,
sortOrder: SortOrder = SortOrder.DESC,
): Omit<ChangeRequestSearchRequest, "pagination"> {
const selectedStateId = filters.stateId ? Number(filters.stateId) : undefined;
const resolvedIds = actionRequired
Expand All @@ -225,8 +229,8 @@ export function buildChangeRequestSearchRequest(
impactKey: filters.impactId ? Number(filters.impactId) : undefined,
},
sortBy: {
field: ChangeRequestSortField.UpdatedOn,
order: SortOrder.DESC,
field: sortField,
order: sortOrder,
},
};
}
Expand Down