Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TAA-136: MVP of the Approval Request and Approval Request List page functionality #576

Merged
merged 18 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c979b49
feat(TAA-163): create approval requests module and placeholder templates
KlimekM Jan 7, 2025
548391f
feat(TAA-163): build out initial Approval Request skeleton UI with mo…
KlimekM Jan 8, 2025
e1e3c55
feat(TAA-163): build out initial Approval Request List skeleton UI wi…
KlimekM Jan 10, 2025
a378675
feat(TAA-163): wire up individual approval request to first API endpoint
KlimekM Jan 24, 2025
aa08970
feat(TAA-163): begin integrating with the decision API
KlimekM Jan 29, 2025
bd686d0
feat(TAA-163): wire up the Approval Request List to the REST API and…
KlimekM Feb 4, 2025
c44cbc5
fix(TAA-136): addresses type error, wires up now returning decision n…
KlimekM Feb 5, 2025
e0b910c
fix(TAA-136): commit assets/ changes, without the custom_theme was th…
KlimekM Feb 5, 2025
6e2bf42
feat(TAA-163): add Breadcrumbs to the Approval Request page
KlimekM Feb 5, 2025
0c77487
test(TAA-163): begin adding tests for the individual approval-request…
KlimekM Feb 5, 2025
350abd6
feat(TAA-163): add hardcoded link in dropdown for Approval requests
KlimekM Feb 6, 2025
b828012
fix(TAA-364): update decision textarea to match design and include av…
KlimekM Feb 10, 2025
3968b2c
fix(TAA-370): remove border radius, add sortable sent on cell, and ad…
KlimekM Feb 10, 2025
e574128
fix: commit generated bundle file updates
KlimekM Feb 10, 2025
9ae1976
feat: update to use translation function with English fallbacks
KlimekM Feb 11, 2025
f9fd58b
test: finish writing unit tests for ApprovalRequests
KlimekM Feb 11, 2025
a2fb5d7
feat(TAA-136): address PR feedback re: using constant and missed tran…
KlimekM Feb 18, 2025
7d1ddab
chore(TAA-136): colocate tests based on PR feedback
KlimekM Feb 19, 2025
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
4 changes: 3 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ module.exports = {
"@typescript-eslint/consistent-type-imports": "error",
"check-file/folder-naming-convention": [
"error",
{ "src/**/": "KEBAB_CASE" },
{
"src/**/!(__tests__)/": "KEBAB_CASE",
},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@luis-almeida I ended up adding a __tests__ directory within the approval-requests module and this was throwing an eslint error on those files. Let me know if you want to handle this differently.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to colocate the tests in 7d1ddab which removed the need for the eslint updates

],
"check-file/filename-naming-convention": [
"error",
Expand Down
554 changes: 554 additions & 0 deletions assets/approval-requests-bundle.js

Large diffs are not rendered by default.

131 changes: 130 additions & 1 deletion assets/flash-notifications-bundle.js

Large diffs are not rendered by default.

1,162 changes: 1,127 additions & 35 deletions assets/new-request-form-bundle.js

Large diffs are not rendered by default.

3,656 changes: 3,655 additions & 1 deletion assets/new-request-form-translations-bundle.js

Large diffs are not rendered by default.

1,115 changes: 1,028 additions & 87 deletions assets/service-catalog-bundle.js

Large diffs are not rendered by default.

2,003 changes: 2,002 additions & 1 deletion assets/service-catalog-translations-bundle.js

Large diffs are not rendered by default.

39,470 changes: 39,435 additions & 35 deletions assets/shared-bundle.js

Large diffs are not rendered by default.

1,671 changes: 1,663 additions & 8 deletions assets/ticket-fields-bundle.js

Large diffs are not rendered by default.

35 changes: 34 additions & 1 deletion assets/wysiwyg-bundle.js

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@
"@zendeskgarden/container-grid": "^3.0.14",
"@zendeskgarden/container-utilities": "^2.0.2",
"@zendeskgarden/react-accordions": "8.76.9",
"@zendeskgarden/react-avatars": "8.76.9",
"@zendeskgarden/react-breadcrumbs": "8.76.9",
"@zendeskgarden/react-buttons": "8.76.9",
"@zendeskgarden/react-datepickers": "8.76.9",
"@zendeskgarden/react-dropdowns.next": "8.76.9",
"@zendeskgarden/react-grid": "8.76.9",
"@zendeskgarden/react-forms": "8.76.9",
"@zendeskgarden/react-grid": "^8.76.9",
"@zendeskgarden/react-loaders": "8.76.9",
"@zendeskgarden/react-modals": "8.76.9",
"@zendeskgarden/react-notifications": "8.76.9",
"@zendeskgarden/react-pagination": "8.76.9",
"@zendeskgarden/react-tables": "8.76.9",
"@zendeskgarden/react-tags": "8.76.9",
"@zendeskgarden/react-theming": "8.76.9",
"@zendeskgarden/react-tooltips": "8.76.9",
Expand Down Expand Up @@ -66,7 +69,8 @@
"@svgr/rollup": "^8.1.0",
"@testing-library/dom": "^9.3.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/user-event": "^14.4.3",
"@testing-library/react": "^12.1.5",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only using testing-library/react version 12 here because that is what is compatible with react 17 which is the current version of react within the repo

"@testing-library/user-event": "^14.6.1",
"@types/lodash.debounce": "^4.0.9",
"@types/react": "^17.0.62",
"@types/react-dom": "^17.0.20",
Expand Down
1 change: 1 addition & 0 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default defineConfig([
"new-request-form": "src/modules/new-request-form/index.tsx",
"flash-notifications": "src/modules/flash-notifications/index.ts",
"service-catalog": "src/modules/service-catalog/index.tsx",
"approval-requests": "src/modules/approval-requests/index.tsx",
},
output: {
dir: "assets",
Expand Down
115 changes: 115 additions & 0 deletions src/modules/approval-requests/ApprovalRequestListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { memo, useState, useMemo } from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { MD, XXL } from "@zendeskgarden/react-typography";
import { getColorV8 } from "@zendeskgarden/react-theming";
import { Spinner } from "@zendeskgarden/react-loaders";
import { useSearchApprovalRequests } from "./hooks/useSearchApprovalRequests";
import ApprovalRequestListFilters from "./components/approval-request-list/ApprovalRequestListFilters";
import ApprovalRequestListTable from "./components/approval-request-list/ApprovalRequestListTable";

const Container = styled.div`
display: flex;
flex-direction: column;
gap: ${(props) => props.theme.space.lg};
margin-top: ${(props) => props.theme.space.xl}; /* 40px */
`;

const LoadingContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;

const NoApprovalRequestsText = styled(MD)`
color: ${(props) => getColorV8("grey", 600, props.theme)};
`;

export interface ApprovalRequestListPageProps {
baseLocale: string;
helpCenterPath: string;
}

type SortDirection = "asc" | "desc" | undefined;

function ApprovalRequestListPage({
baseLocale,
helpCenterPath,
}: ApprovalRequestListPageProps) {
const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState("");
const [sortDirection, setSortDirection] = useState<SortDirection>(undefined);
const {
approvalRequests,
errorFetchingApprovalRequests: error,
approvalRequestStatus,
setApprovalRequestStatus,
isLoading,
} = useSearchApprovalRequests();

const sortedAndFilteredApprovalRequests = useMemo(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't filtering and sorting be handled via the api?
Also if the results are paginated this would only apply to the first page.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, ideally the filtering and sorting is both handled API side but that has not been implemented yet and we agreed to do this client side for the EAP. The results are currently unpaginated as well and we do have plans to sort/filter on the API long term

let results = [...approvalRequests];

// Apply search filter
if (searchTerm) {
const term = searchTerm.toLowerCase();
results = results.filter((request) =>
request.subject.toLowerCase().includes(term)
);
}

// Apply sorting
if (sortDirection) {
results.sort((a, b) => {
const dateA = new Date(a.created_at).getTime();
const dateB = new Date(b.created_at).getTime();
return sortDirection === "asc" ? dateA - dateB : dateB - dateA;
});
}

return results;
}, [approvalRequests, searchTerm, sortDirection]);

if (error) {
throw error;

Choose a reason for hiding this comment

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

nit: i know this is still MVP but we could note to handle errors gracefully in the future like a growl or something with better UX

Choose a reason for hiding this comment

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

maybe similar to using notify below?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good callout, I followed the pattern used in the Service Catalog: https://github.com/zendesk/copenhagen_theme/blob/beta/src/modules/service-catalog/ServiceCatalogItemPage.tsx#L49-L51

This relies on the ErrorBoundary in the src/modules/approval-requests/renderApprovalRequestList.tsx file which will display this standard error screen:
Screenshot 2025-02-05 at 11 09 08 AM

}

if (isLoading) {
return (
<LoadingContainer>
<Spinner size="64" />
</LoadingContainer>
);
}

return (
<Container>
<XXL isBold>
{t("approval-requests.list.header", "Approval requests")}
</XXL>
<ApprovalRequestListFilters
approvalRequestStatus={approvalRequestStatus}
setApprovalRequestStatus={setApprovalRequestStatus}
setSearchTerm={setSearchTerm}
/>
{approvalRequests.length === 0 ? (
<NoApprovalRequestsText>
{t(
"approval-requests.list.no-requests",
"No approval requests found."
)}
</NoApprovalRequestsText>
) : (
<ApprovalRequestListTable
approvalRequests={sortedAndFilteredApprovalRequests}
baseLocale={baseLocale}
helpCenterPath={helpCenterPath}
sortDirection={sortDirection}
onSortChange={setSortDirection}
/>
)}
</Container>
);
}

export default memo(ApprovalRequestListPage);
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious why do we need to memoize here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a habit/pre-optimization. In zendesk_console we wrap each component in memo by default to to ensure that the component does not re-render if it's parent component re-renders unless it's props have also changed.

136 changes: 136 additions & 0 deletions src/modules/approval-requests/ApprovalRequestPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { memo } from "react";
import styled from "styled-components";
import { MD, XXL } from "@zendeskgarden/react-typography";
import { Spinner } from "@zendeskgarden/react-loaders";
import ApprovalRequestDetails from "./components/approval-request/ApprovalRequestDetails";
import ApprovalTicketDetails from "./components/approval-request/ApprovalTicketDetails";
import ApproverActions from "./components/approval-request/ApproverActions";
import { useApprovalRequest } from "./hooks/useApprovalRequest";
import type { Organization } from "../ticket-fields";
import ApprovalRequestBreadcrumbs from "./components/approval-request/ApprovalRequestBreadcrumbs";

const Container = styled.div`
display: flex;
flex-direction: row;
margin-top: ${(props) => props.theme.space.xl}; /* 40px */
margin-bottom: ${(props) => props.theme.space.lg}; /* 32px */

@media (max-width: ${(props) => props.theme.breakpoints.md}) {
flex-direction: column;
margin-bottom: ${(props) => props.theme.space.xl}; /* 40px */
}
`;

const LoadingContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;

const LeftColumn = styled.div`
flex: 2;

& > *:first-child {
margin-bottom: ${(props) => props.theme.space.base * 4}px; /* 16px */
}

& > *:not(:first-child) {
margin-bottom: ${(props) => props.theme.space.lg}; /* 32px */
}

& > *:last-child {
margin-bottom: 0;
}

@media (max-width: ${(props) => props.theme.breakpoints.md}) {
flex: 1;
margin-right: 0;
margin-bottom: ${(props) => props.theme.space.lg};
}
`;

const RightColumn = styled.div`
flex: 1;
margin-left: ${(props) => props.theme.space.base * 6}px; /* 24px */

@media (max-width: ${(props) => props.theme.breakpoints.md}) {
margin-left: 0;
}
`;

export interface ApprovalRequestPageProps {
approvalWorkflowInstanceId: string;
approvalRequestId: string;
baseLocale: string;
helpCenterPath: string;
organizations: Array<Organization>;
userId: number;
}

function ApprovalRequestPage({
approvalWorkflowInstanceId,
approvalRequestId,
baseLocale,
helpCenterPath,
organizations,
userId,
}: ApprovalRequestPageProps) {
const {
approvalRequest,
setApprovalRequest,
errorFetchingApprovalRequest: error,
isLoading,
} = useApprovalRequest(approvalWorkflowInstanceId, approvalRequestId);

if (error) {
throw error;
}

if (isLoading) {
return (
<LoadingContainer>
<Spinner size="64" />
</LoadingContainer>
);
}

const showApproverActions =
userId === approvalRequest?.assignee_user?.id &&
approvalRequest?.status === "active";

return (
<>
<ApprovalRequestBreadcrumbs
helpCenterPath={helpCenterPath}
organizations={organizations}
/>
<Container>
<LeftColumn>
<XXL isBold>{approvalRequest?.subject}</XXL>
<MD>{approvalRequest?.message}</MD>
{approvalRequest?.ticket_details && (
<ApprovalTicketDetails ticket={approvalRequest.ticket_details} />
)}
</LeftColumn>
<RightColumn>
{approvalRequest && (
<ApprovalRequestDetails
approvalRequest={approvalRequest}
baseLocale={baseLocale}
/>
)}
</RightColumn>
</Container>
{showApproverActions && (
<ApproverActions
approvalWorkflowInstanceId={approvalWorkflowInstanceId}
approvalRequestId={approvalRequestId}
setApprovalRequest={setApprovalRequest}
assigneeUser={approvalRequest?.assignee_user}
/>
)}
</>
);
}

export default memo(ApprovalRequestPage);
Loading
Loading