diff --git a/CHANGELOG.md b/CHANGELOG.md index 885648389c5..315869e52d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,11 +22,11 @@ Changes can also be flagged with a GitHub label for tracking purposes. The URL o ## [Unreleased](https://github.com/ethyca/fides/compare/2.62.0...main) ### Added +- Added ability to add internal comments to privacy requests [#6165](https://github.com/ethyca/fides/pull/6165) - Attachments can now be stored with GCS [#6161](https://github.com/ethyca/fides/pull/6161) - Attachments can now retrieve their content as well as their download urls [#6169 ](https://github.com/ethyca/fides/pull/6169) - ## [2.62.0](https://github.com/ethyca/fides/compare/2.61.1...2.62.0) ### Added diff --git a/clients/admin-ui/cypress/e2e/privacy-requests.cy.ts b/clients/admin-ui/cypress/e2e/privacy-requests.cy.ts index c0d90e5ae7e..a722bce8af1 100644 --- a/clients/admin-ui/cypress/e2e/privacy-requests.cy.ts +++ b/clients/admin-ui/cypress/e2e/privacy-requests.cy.ts @@ -512,4 +512,125 @@ describe("Privacy Requests", () => { ); }); }); + + /** + * Tests for privacy request comments functionality + */ + describe("Request Comments", () => { + beforeEach(() => { + cy.assumeRole(RoleRegistryEnum.OWNER); + + cy.intercept("GET", "/api/v1/plus/privacy-request/*/comment*", { + statusCode: 200, + fixture: "privacy-requests/comments/comments-list.json", + }).as("getComments"); + + cy.intercept("POST", "/api/v1/plus/privacy-request/*/comment", { + statusCode: 200, + fixture: "privacy-requests/comments/comment-created.json", + }).as("createComment"); + + cy.intercept("GET", "/api/v1/privacy-request*", { + fixture: "privacy-requests/with-logs.json", + }).as("getPrivacyRequestWithLogs"); + + cy.visit("/privacy-requests/pri_96bb91d3-cdb9-46c3-9546-0c276eb05a5c"); + cy.wait("@getPrivacyRequestWithLogs"); + cy.wait("@getComments"); + }); + + it("displays existing comments in the activity timeline", () => { + cy.getByTestId("activity-timeline-item") + .contains("This is a test comment") + .should("exist"); + cy.contains("Test User:").should("exist"); + cy.getByTestId("activity-timeline-type") + .contains("Internal comment") + .should("exist"); + }); + + it("allows creating a new comment", () => { + cy.getByTestId("add-comment-button").click(); + cy.getByTestId("comment-input").should("exist"); + cy.getByTestId("comment-input").type("New comment from test"); + cy.getByTestId("submit-comment-button").click(); + + // Check that the request was made with the correct form data + cy.wait("@createComment").then((interception) => { + const requestBody = interception.request.body; + expect(requestBody).to.include('name="comment_text"'); + expect(requestBody).to.include("New comment from test"); + expect(requestBody).to.include('name="comment_type"'); + expect(requestBody).to.include("note"); + }); + }); + + it("allows canceling comment creation", () => { + cy.getByTestId("add-comment-button").click(); + cy.getByTestId("comment-input").should("exist"); + cy.getByTestId("comment-input").type("Comment that will be canceled"); + cy.getByTestId("cancel-comment-button").click(); + cy.getByTestId("comment-input").should("not.exist"); + }); + + it("shows loading state while fetching comments", () => { + cy.intercept("GET", "/api/v1/plus/privacy-request/*/comment*", { + statusCode: 200, + fixture: "privacy-requests/comments/empty-comments.json", + delay: 1000, + }).as("getCommentsDelayed"); + + cy.visit("/privacy-requests/pri_96bb91d3-cdb9-46c3-9546-0c276eb05a5c"); + + // Check for skeleton loading state before comments load + cy.getByTestId("timeline-skeleton").should("exist"); + + cy.wait("@getCommentsDelayed"); + + // Verify skeletons are gone after loading + cy.getByTestId("timeline-skeleton").should("not.exist"); + }); + + it("restricts comment functionality based on user permissions", () => { + // First check with a viewer role (has comment:read but not comment:create) + cy.assumeRole(RoleRegistryEnum.VIEWER); + cy.reload(); + cy.wait("@getPrivacyRequestWithLogs"); + cy.wait("@getComments"); + + // Button should be hidden for users without COMMENT_CREATE scope + cy.getByTestId("add-comment-button").should("not.exist"); + + // Then check with an owner role (has comment:create scope) + cy.assumeRole(RoleRegistryEnum.OWNER); + cy.reload(); + cy.wait("@getPrivacyRequestWithLogs"); + cy.wait("@getComments"); + + // Button should be visible for users with COMMENT_CREATE scope + cy.getByTestId("add-comment-button").should("exist"); + }); + + it("handles 404 errors from comments API gracefully", () => { + // Intercept comments API and return a 404 error + cy.intercept("GET", "/api/v1/plus/privacy-request/*/comment*", { + statusCode: 404, + body: { + detail: "Not found", + }, + }).as("commentsNotFound"); + + // Load the page + cy.visit("/privacy-requests/pri_96bb91d3-cdb9-46c3-9546-0c276eb05a5c"); + cy.wait("@getPrivacyRequestWithLogs"); + cy.wait("@commentsNotFound"); + + // Verify the timeline still shows request updates even if comments failed to load + cy.getByTestId("activity-timeline-list").should("exist"); + cy.getByTestId("activity-timeline-item").should("exist"); + + // The Add comment button should still be available + cy.getByTestId("add-comment-button").should("exist"); + }); + }); }); diff --git a/clients/admin-ui/cypress/fixtures/privacy-requests/comments/comment-created.json b/clients/admin-ui/cypress/fixtures/privacy-requests/comments/comment-created.json new file mode 100644 index 00000000000..9e4be199a23 --- /dev/null +++ b/clients/admin-ui/cypress/fixtures/privacy-requests/comments/comment-created.json @@ -0,0 +1,11 @@ +{ + "id": "comment_456", + "privacy_request_id": "pri_123", + "comment_text": "New comment from test", + "comment_type": "NOTE", + "created_at": "2023-01-02T12:00:00Z", + "user_id": "usr_123", + "username": "testuser", + "user_first_name": "Test", + "user_last_name": "User" +} diff --git a/clients/admin-ui/cypress/fixtures/privacy-requests/comments/comments-list.json b/clients/admin-ui/cypress/fixtures/privacy-requests/comments/comments-list.json new file mode 100644 index 00000000000..ec0356b8301 --- /dev/null +++ b/clients/admin-ui/cypress/fixtures/privacy-requests/comments/comments-list.json @@ -0,0 +1,18 @@ +{ + "items": [ + { + "id": "comment_123", + "privacy_request_id": "pri_123", + "comment_text": "This is a test comment", + "comment_type": "NOTE", + "created_at": "2023-01-01T12:00:00Z", + "user_id": "usr_123", + "username": "testuser", + "user_first_name": "Test", + "user_last_name": "User" + } + ], + "total": 1, + "page": 1, + "size": 10 +} diff --git a/clients/admin-ui/cypress/fixtures/privacy-requests/comments/empty-comments.json b/clients/admin-ui/cypress/fixtures/privacy-requests/comments/empty-comments.json new file mode 100644 index 00000000000..871c13aedaf --- /dev/null +++ b/clients/admin-ui/cypress/fixtures/privacy-requests/comments/empty-comments.json @@ -0,0 +1,6 @@ +{ + "items": [], + "total": 0, + "page": 1, + "size": 10 +} diff --git a/clients/admin-ui/cypress/fixtures/scopes/roles-to-scopes.json b/clients/admin-ui/cypress/fixtures/scopes/roles-to-scopes.json index 7bcb3d301b8..57abd508030 100644 --- a/clients/admin-ui/cypress/fixtures/scopes/roles-to-scopes.json +++ b/clients/admin-ui/cypress/fixtures/scopes/roles-to-scopes.json @@ -18,6 +18,8 @@ "client:delete", "client:read", "client:update", + "comment:create", + "comment:read", "config:read", "config:update", "connection:authorize", @@ -157,6 +159,7 @@ "classify_instance:read", "cli-objects:read", "client:read", + "comment:read", "connection:read", "connection_type:read", "consent:read", @@ -201,6 +204,7 @@ "classify_instance:read", "cli-objects:read", "client:read", + "comment:read", "connection:read", "connection_type:read", "consent:read", @@ -237,6 +241,8 @@ "webhook:read" ], "approver": [ + "comment:create", + "comment:read", "privacy-request:read", "privacy-request:resume", "privacy-request:review", @@ -259,6 +265,8 @@ "client:delete", "client:read", "client:update", + "comment:create", + "comment:read", "config:read", "config:update", "connection:authorize", diff --git a/clients/admin-ui/src/features/common/ClipboardButton.tsx b/clients/admin-ui/src/features/common/ClipboardButton.tsx index 54920fa3a32..f4a52992a41 100644 --- a/clients/admin-ui/src/features/common/ClipboardButton.tsx +++ b/clients/admin-ui/src/features/common/ClipboardButton.tsx @@ -2,12 +2,11 @@ import { AntButton as Button, AntButtonProps as ButtonProps, AntTooltip as Tooltip, + Icons, useClipboard, } from "fidesui"; import React, { useState } from "react"; -import { CopyIcon } from "./Icon"; - enum TooltipText { COPY = "Copy", COPIED = "Copied!", @@ -51,7 +50,7 @@ const ClipboardButton = ({ copyText, ...props }: ClipboardButtonProps) => { }} > + + + )} + + ); +}; + +export default ActivityTab; diff --git a/clients/admin-ui/src/features/privacy-requests/events-and-logs/ActivityTimeline.tsx b/clients/admin-ui/src/features/privacy-requests/events-and-logs/ActivityTimeline.tsx index d9dca7f90d0..1c87685ae84 100644 --- a/clients/admin-ui/src/features/privacy-requests/events-and-logs/ActivityTimeline.tsx +++ b/clients/admin-ui/src/features/privacy-requests/events-and-logs/ActivityTimeline.tsx @@ -1,12 +1,21 @@ -import { Box, useDisclosure } from "fidesui"; import { + AntList as List, + AntSkeleton as Skeleton, + Box, + useDisclosure, +} from "fidesui"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; + +import { + ActivityTimelineItemTypeEnum, ExecutionLog, ExecutionLogStatus, PrivacyRequestEntity, -} from "privacy-requests/types"; -import React, { useEffect, useState } from "react"; +} from "~/features/privacy-requests/types"; -import ActivityTimelineList from "./ActivityTimelineList"; +import ActivityTimelineEntry from "./ActivityTimelineEntry"; +import styles from "./ActivityTimelineEntry.module.scss"; +import { usePrivacyRequestComments, usePrivacyRequestEventLogs } from "./hooks"; import LogDrawer from "./LogDrawer"; type ActivityTimelineProps = { @@ -23,9 +32,15 @@ const ActivityTimeline = ({ subjectRequest }: ActivityTimelineProps) => { ExecutionLogStatus.ERROR, ); - const { results } = subjectRequest; + const { results, id: privacyRequestId } = subjectRequest; + + const { commentItems, isLoading: isCommentsLoading } = + usePrivacyRequestComments(privacyRequestId); + const { eventItems, isLoading: isResultsLoading } = + usePrivacyRequestEventLogs(results); + + const isLoading = isCommentsLoading || isResultsLoading; - // Update currentLogs when results change and we have a selected key useEffect(() => { if (currentKey && results && results[currentKey]) { setCurrentLogs(results[currentKey]); @@ -52,18 +67,77 @@ const ActivityTimeline = ({ subjectRequest }: ActivityTimelineProps) => { onClose(); }; - const showLogs = (key: string, logs: ExecutionLog[]) => { - setCurrentKey(key); - setCurrentLogs(logs); - onOpen(); - }; + const showLogs = useCallback( + (key: string, logs: ExecutionLog[]) => { + setCurrentKey(key); + setCurrentLogs(logs); + onOpen(); + }, + [onOpen], + ); + + const timelineItems = useMemo(() => { + const eventItemsWithClickHandler = eventItems.map((item) => { + if (item.type === "Request update" && item.title && results) { + const key = item.title; + if (results[key]) { + return { + ...item, + onClick: () => showLogs(key, results[key]), + }; + } + } + return item; + }); + + // Create initial access request item + const initialRequestItem = { + author: "Fides", + title: "Access request received", + date: new Date(subjectRequest.created_at), + type: ActivityTimelineItemTypeEnum.REQUEST_UPDATE, + showViewLog: false, + isError: false, + isSkipped: false, + id: "initial-request", + }; + + const allItems = [ + initialRequestItem, + ...eventItemsWithClickHandler, + ...commentItems, + ]; + + // Sort by date (oldest first) + return allItems.sort((a, b) => { + return new Date(a.date).getTime() - new Date(b.date).getTime(); + }); + }, [eventItems, commentItems, results, showLogs, subjectRequest.created_at]); + + const renderSkeletonItems = () => ( +
+ +
+ ); return ( - showLogs(key, logs)} - /> + +
    + {isLoading + ? renderSkeletonItems() + : timelineItems.map((item) => ( +
  • + +
  • + ))} +
+
{ - const [isOpen, setIsOpen] = useState(false); - - return ( -
- - {content && {content}} -
- {logs} +const ActivityTimelineEntry = ({ item }: ActivityTimelineEntryProps) => { + const { + author, + title, + date, + type, + onClick, + isError, + isSkipped, + description, + } = item; + + // Format the date for display + const formattedDate = formatDate(date); + + const isClickable = !!onClick; + + const content = ( + <> +
+ + {author}: + + {title && ( + + {title} + {isError && " failed"} + + )} + + {formattedDate} + + + {type} + + {(isError || isSkipped) && ( + + View Log + + )}
-
+ {description && ( +
+ + {description} + +
+ )} + + ); + + const commonProps = { + className: classNames(styles.itemButton, { + [styles["itemButton--error"]]: isError, + [styles["itemButton--clickable"]]: isClickable, + [styles["itemButton--comment"]]: + type === ActivityTimelineItemTypeEnum.INTERNAL_COMMENT, + }), + "data-testid": "activity-timeline-item", + }; + + // Render a button for clickable items, or a div for non-clickable items + // This maintains the same styling and data-testid attributes while changing the HTML element + return isClickable ? ( + + ) : ( +
{content}
); }; + export default ActivityTimelineEntry; diff --git a/clients/admin-ui/src/features/privacy-requests/events-and-logs/ActivityTimelineList.module.scss b/clients/admin-ui/src/features/privacy-requests/events-and-logs/ActivityTimelineList.module.scss deleted file mode 100644 index 5757ecbe1ed..00000000000 --- a/clients/admin-ui/src/features/privacy-requests/events-and-logs/ActivityTimelineList.module.scss +++ /dev/null @@ -1,68 +0,0 @@ -$border-width: 1px; -$horizontal-padding: 20px; - -.itemButton { - display: block; - width: 100%; - border: $border-width solid transparent; - - border-radius: 6px; - transition: border-color 0.2s ease-in-out; - margin-bottom: 20px; - padding: 12px $horizontal-padding; - - &:hover, - &--error { - border-color: var(--fidesui-neutral-100); - } - - &:focus-visible { - border-color: var(--fidesui-neutral-700); - } - - &--error, - &--error:hover, - &--error:focus { - border-left: 8px solid var(--fidesui-error); - } -} - -.header { - cursor: pointer; - width: 100%; - - display: flex; - flex-wrap: wrap; - gap: 8px; - align-items: center; -} - -.title { - font-weight: 600; - - &--error { - color: var(--fidesui-error); - } -} - -.timestamp { - color: var(--fidesui-neutral-700); -} - -.viewLogs { - color: var(--fidesui-link); -} - -.logs { - height: 0; - overflow: hidden; - transition: height 0.2s ease-in-out; - box-sizing: border-box; - padding: 0 $horizontal-padding; - - &--open { - height: auto; - margin-top: 20px; - margin-bottom: 20px; - } -} diff --git a/clients/admin-ui/src/features/privacy-requests/events-and-logs/ActivityTimelineList.tsx b/clients/admin-ui/src/features/privacy-requests/events-and-logs/ActivityTimelineList.tsx deleted file mode 100644 index ceb7c2e431b..00000000000 --- a/clients/admin-ui/src/features/privacy-requests/events-and-logs/ActivityTimelineList.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import classNames from "classnames"; -import { AntList as List, AntTag as Tag } from "fidesui"; -import { map } from "lodash"; -import { useCallback } from "react"; - -import { formatDate } from "~/features/common/utils"; -import { ExecutionLogStatus } from "~/types/api"; - -import { ExecutionLog, PrivacyRequestResults } from "../types"; -import styles from "./ActivityTimelineList.module.scss"; - -interface ActivityTimelineItem { - // eslint-disable-next-line react/no-unused-prop-types - logs: ExecutionLog[]; - // eslint-disable-next-line react/no-unused-prop-types - key: string; -} - -interface ActivityTimelineProps { - results?: PrivacyRequestResults; - onItemClicked: ({ key, logs }: ActivityTimelineItem) => void; -} - -const ActivityTimelineList = ({ - results, - onItemClicked, -}: ActivityTimelineProps) => { - const items: ActivityTimelineItem[] = map(results, (logs, key) => ({ - logs, - key, - })); - - const renderItem = useCallback( - ({ logs, key }: ActivityTimelineItem) => { - const hasUnresolvedError = logs.some( - (log) => log.status === ExecutionLogStatus.ERROR, - ); - const hasSkippedEntry = logs.some( - (log) => log.status === ExecutionLogStatus.SKIPPED, - ); - - return ( - - ); - }, - [onItemClicked], - ); - - return ( - - {items.map(renderItem)} - - ); -}; - -export default ActivityTimelineList; diff --git a/clients/admin-ui/src/features/privacy-requests/events-and-logs/CommentInput.tsx b/clients/admin-ui/src/features/privacy-requests/events-and-logs/CommentInput.tsx new file mode 100644 index 00000000000..33f39eb53ee --- /dev/null +++ b/clients/admin-ui/src/features/privacy-requests/events-and-logs/CommentInput.tsx @@ -0,0 +1,96 @@ +import { + AntButton as Button, + AntFlex as Flex, + AntInput as Input, + AntMessage as message, + AntTabs as Tabs, + AntTabsProps as TabsProps, +} from "fidesui"; +import { useEffect, useRef, useState } from "react"; + +import { CommentType } from "~/types/api/models/CommentType"; + +import { useCreateCommentMutation } from "../comments/privacy-request-comments.slice"; + +export interface CommentInputProps { + privacyRequestId: string; + onCancel: () => void; +} + +export const CommentInput = ({ + privacyRequestId, + onCancel, +}: CommentInputProps) => { + const [commentText, setCommentText] = useState(""); + const textAreaRef = useRef(null); + const [createComment, { isLoading }] = useCreateCommentMutation(); + + // Focus the textarea when the component mounts + useEffect(() => { + if (textAreaRef.current) { + textAreaRef.current.focus(); + } + }, []); + + const handleSubmit = async () => { + if (commentText.trim()) { + try { + await createComment({ + privacy_request_id: privacyRequestId, + comment_text: commentText, + comment_type: CommentType.NOTE, + }).unwrap(); + + // Reset and close + setCommentText(""); + onCancel(); + } catch (error) { + // eslint-disable-next-line no-console + console.error("Failed to add comment:", error); + message.error({ + content: "Failed to add comment", + duration: 5, + }); + } + } + }; + + const items: TabsProps["items"] = [ + { + key: "internal", + label: "Internal comment", + children: ( + setCommentText(e.target.value)} + rows={3} + className="mb-3 h-[150px] w-full !resize-none" + data-testid="comment-input" + /> + ), + }, + ]; + + return ( +
+ + + + + + +
+ ); +}; diff --git a/clients/admin-ui/src/features/privacy-requests/events-and-logs/hooks/index.ts b/clients/admin-ui/src/features/privacy-requests/events-and-logs/hooks/index.ts new file mode 100644 index 00000000000..c878eedfdaa --- /dev/null +++ b/clients/admin-ui/src/features/privacy-requests/events-and-logs/hooks/index.ts @@ -0,0 +1,2 @@ +export * from "./usePrivacyRequestComments"; +export * from "./usePrivacyRequestEventLogs"; diff --git a/clients/admin-ui/src/features/privacy-requests/events-and-logs/hooks/usePrivacyRequestComments.ts b/clients/admin-ui/src/features/privacy-requests/events-and-logs/hooks/usePrivacyRequestComments.ts new file mode 100644 index 00000000000..5ae0442887e --- /dev/null +++ b/clients/admin-ui/src/features/privacy-requests/events-and-logs/hooks/usePrivacyRequestComments.ts @@ -0,0 +1,57 @@ +import { AntMessage as message } from "fidesui"; +import { useEffect } from "react"; + +import { useGetCommentsQuery } from "~/features/privacy-requests/comments/privacy-request-comments.slice"; +import { + ActivityTimelineItem, + ActivityTimelineItemTypeEnum, +} from "~/features/privacy-requests/types"; +import { CommentResponse } from "~/types/api/models/CommentResponse"; + +/** + * Hook for fetching and processing privacy request comments + */ +export const usePrivacyRequestComments = (privacyRequestId: string) => { + // Fetch comments data for this privacy request + const { + data: commentsData, + isLoading, + error, + } = useGetCommentsQuery({ + privacy_request_id: privacyRequestId, + size: 100, // Use a reasonable limit + }); + + // Handle error state + useEffect(() => { + if (error) { + message.error("Failed to fetch the request comments"); + } + }, [error]); + + // Map comments to ActivityTimelineItem + const commentItems: ActivityTimelineItem[] = !commentsData?.items + ? [] + : commentsData.items.map((comment: CommentResponse) => { + const author = + comment.user_first_name && comment.user_last_name + ? `${comment.user_first_name} ${comment.user_last_name}` + : comment.username || "Unknown"; + + return { + author, + date: new Date(comment.created_at), + type: ActivityTimelineItemTypeEnum.INTERNAL_COMMENT, + showViewLog: false, + description: comment.comment_text, + isError: false, + isSkipped: false, + id: `comment-${comment.id}`, + }; + }); + + return { + commentItems, + isLoading, + }; +}; diff --git a/clients/admin-ui/src/features/privacy-requests/events-and-logs/hooks/usePrivacyRequestEventLogs.ts b/clients/admin-ui/src/features/privacy-requests/events-and-logs/hooks/usePrivacyRequestEventLogs.ts new file mode 100644 index 00000000000..974a606a411 --- /dev/null +++ b/clients/admin-ui/src/features/privacy-requests/events-and-logs/hooks/usePrivacyRequestEventLogs.ts @@ -0,0 +1,43 @@ +import { + ActivityTimelineItem, + ActivityTimelineItemTypeEnum, + ExecutionLogStatus, + PrivacyRequestResults, +} from "~/features/privacy-requests/types"; + +/** + * Hook for processing privacy request event logs + */ +export const usePrivacyRequestEventLogs = (results?: PrivacyRequestResults) => { + // Determine if results are loading + const isLoading = !results; + + // Map from source events to ActivityTimelineItems + const eventItems: ActivityTimelineItem[] = !results + ? [] + : Object.entries(results).map(([key, logs]) => { + const hasUnresolvedError = logs.some( + (log) => log.status === ExecutionLogStatus.ERROR, + ); + const hasSkippedEntry = logs.some( + (log) => log.status === ExecutionLogStatus.SKIPPED, + ); + + return { + author: "Fides", + title: key, + date: new Date(logs[0].updated_at), + type: ActivityTimelineItemTypeEnum.REQUEST_UPDATE, + showViewLog: hasUnresolvedError || hasSkippedEntry, + onClick: () => {}, // This will be overridden in the component + isError: hasUnresolvedError, + isSkipped: hasSkippedEntry, + id: `request-${key}`, + }; + }); + + return { + eventItems, + isLoading, + }; +}; diff --git a/clients/admin-ui/src/features/privacy-requests/types.ts b/clients/admin-ui/src/features/privacy-requests/types.ts index 953c63a5f64..11168143e74 100644 --- a/clients/admin-ui/src/features/privacy-requests/types.ts +++ b/clients/admin-ui/src/features/privacy-requests/types.ts @@ -207,3 +207,29 @@ export interface ConfigMessagingSecretsRequest { twilio_sender_phone_number?: string; }; } + +export enum ActivityTimelineItemTypeEnum { + REQUEST_UPDATE = "Request update", + INTERNAL_COMMENT = "Internal comment", +} + +export const TimelineItemColorMap: Record< + ActivityTimelineItemTypeEnum, + string +> = { + [ActivityTimelineItemTypeEnum.REQUEST_UPDATE]: "sandstone", + [ActivityTimelineItemTypeEnum.INTERNAL_COMMENT]: "marble", +}; + +export interface ActivityTimelineItem { + author: string; + title?: string; + date: Date; + type: ActivityTimelineItemTypeEnum; + showViewLog: boolean; + onClick?: () => void; + description?: string; + isError: boolean; + isSkipped: boolean; + id: string; +} diff --git a/clients/admin-ui/src/types/api/index.ts b/clients/admin-ui/src/types/api/index.ts index dbe1240d7ff..415dabad83e 100644 --- a/clients/admin-ui/src/types/api/index.ts +++ b/clients/admin-ui/src/types/api/index.ts @@ -33,7 +33,7 @@ export type { BasicSystemResponse } from "./models/BasicSystemResponse"; export type { BigQueryConfig } from "./models/BigQueryConfig"; export type { BigQueryDocsSchema } from "./models/BigQueryDocsSchema"; export type { Body_acquire_access_token_api_v1_oauth_token_post } from "./models/Body_acquire_access_token_api_v1_oauth_token_post"; -export type { Body_create_privacy_request_attachment_api_v1_plus_privacy_request__privacy_request_id__attachment_post } from "./models/Body_create_privacy_request_attachment_api_v1_plus_privacy_request__privacy_request_id__attachment_post"; +export type { Body_create_comment_api_v1_plus_privacy_request__privacy_request_id__comment_post } from "./models/Body_create_comment_api_v1_plus_privacy_request__privacy_request_id__comment_post"; export type { Body_export_minimal_datamap_with_format_api_v1_plus_datamap_minimal__export_format__post } from "./models/Body_export_minimal_datamap_with_format_api_v1_plus_datamap_minimal__export_format__post"; export type { Body_upload_data_api_v1_storage__request_id__post } from "./models/Body_upload_data_api_v1_storage__request_id__post"; export type { BulkCustomFieldRequest } from "./models/BulkCustomFieldRequest"; diff --git a/clients/admin-ui/src/types/api/models/Body_create_privacy_request_attachment_api_v1_plus_privacy_request__privacy_request_id__attachment_post.ts b/clients/admin-ui/src/types/api/models/Body_create_attachment_api_v1_plus_privacy_request__privacy_request_id__attachment_post.ts similarity index 62% rename from clients/admin-ui/src/types/api/models/Body_create_privacy_request_attachment_api_v1_plus_privacy_request__privacy_request_id__attachment_post.ts rename to clients/admin-ui/src/types/api/models/Body_create_attachment_api_v1_plus_privacy_request__privacy_request_id__attachment_post.ts index 02c097ac211..bc949d18d09 100644 --- a/clients/admin-ui/src/types/api/models/Body_create_privacy_request_attachment_api_v1_plus_privacy_request__privacy_request_id__attachment_post.ts +++ b/clients/admin-ui/src/types/api/models/Body_create_attachment_api_v1_plus_privacy_request__privacy_request_id__attachment_post.ts @@ -4,7 +4,7 @@ import type { AttachmentType } from "./AttachmentType"; -export type Body_create_privacy_request_attachment_api_v1_plus_privacy_request__privacy_request_id__attachment_post = +export type Body_create_attachment_api_v1_plus_privacy_request__privacy_request_id__attachment_post = { attachment_type: AttachmentType; attachment_file: Blob; diff --git a/clients/admin-ui/src/types/api/models/Body_create_privacy_request_comment_api_v1_plus_privacy_request__privacy_request_id__comment_post.ts b/clients/admin-ui/src/types/api/models/Body_create_comment_api_v1_plus_privacy_request__privacy_request_id__comment_post.ts similarity index 73% rename from clients/admin-ui/src/types/api/models/Body_create_privacy_request_comment_api_v1_plus_privacy_request__privacy_request_id__comment_post.ts rename to clients/admin-ui/src/types/api/models/Body_create_comment_api_v1_plus_privacy_request__privacy_request_id__comment_post.ts index c7abbd35b2f..2fef4454559 100644 --- a/clients/admin-ui/src/types/api/models/Body_create_privacy_request_comment_api_v1_plus_privacy_request__privacy_request_id__comment_post.ts +++ b/clients/admin-ui/src/types/api/models/Body_create_comment_api_v1_plus_privacy_request__privacy_request_id__comment_post.ts @@ -5,7 +5,7 @@ import type { AttachmentType } from "./AttachmentType"; import type { CommentType } from "./CommentType"; -export type Body_create_privacy_request_comment_api_v1_plus_privacy_request__privacy_request_id__comment_post = +export type Body_create_comment_api_v1_plus_privacy_request__privacy_request_id__comment_post = { comment_text: string; comment_type: CommentType; diff --git a/clients/admin-ui/src/types/api/models/Page_CommentResponse_.ts b/clients/admin-ui/src/types/api/models/Page_CommentResponse_.ts new file mode 100644 index 00000000000..2e5207fd995 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/Page_CommentResponse_.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { CommentResponse } from "./CommentResponse"; + +export type Page_CommentResponse_ = { + items: Array; + total: number | null; + page: number | null; + size: number | null; + pages?: number | null; +}; diff --git a/clients/admin-ui/src/types/api/models/ScopeRegistryEnum.ts b/clients/admin-ui/src/types/api/models/ScopeRegistryEnum.ts index ef3a4959ebe..29e1d678511 100644 --- a/clients/admin-ui/src/types/api/models/ScopeRegistryEnum.ts +++ b/clients/admin-ui/src/types/api/models/ScopeRegistryEnum.ts @@ -21,6 +21,8 @@ export enum ScopeRegistryEnum { CLIENT_DELETE = "client:delete", CLIENT_READ = "client:read", CLIENT_UPDATE = "client:update", + COMMENT_CREATE = "comment:create", + COMMENT_READ = "comment:read", CONFIG_READ = "config:read", CONFIG_UPDATE = "config:update", CONNECTION_AUTHORIZE = "connection:authorize",