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

feat(web): version history #1409

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open

feat(web): version history #1409

wants to merge 9 commits into from

Conversation

caichi-t
Copy link
Contributor

@caichi-t caichi-t commented Feb 17, 2025

Overview

What I've done

What I haven't done

How I tested

Screenshot

Which point I want you to review particularly

Memo

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive version management for content items. Users can now switch between "Meta Data" and "Version History" views to see detailed historical information, including creation dates and contributors.
    • Added support for handling versioned items within the content management interface.
    • Implemented a new FormItemsWrapper component for dynamic rendering of various field types.
  • Refactor

    • Streamlined sidebar functionality by removing linked request navigation.
    • Enhanced data refresh processes to ensure the most current version information is displayed promptly.
    • Updated component props for better clarity and consistency, particularly with the introduction of referencedItems.
  • Style

    • Refined sidebar layout with improved borders and padding for a cleaner, more organized interface.
    • Updated the visual presentation of the PageHeader across multiple components to ensure a consistent white background.

@caichi-t caichi-t requested a review from nourbalaha as a code owner February 17, 2025 12:04
Copy link
Contributor

coderabbitai bot commented Feb 17, 2025

Walkthrough

The pull request introduces version-management enhancements throughout the codebase. A new VersionedItem type and accompanying versions prop are added to several components, enabling them to handle versioned content. UI changes include a tabbed sidebar for metadata and historical versions, while the GraphQL layer is extended with new queries and hooks to support fetching version data. Additionally, obsolete linked requests functionality is removed, and mutation handlers are updated to refetch relevant queries.

Changes

File(s) Change Summary
web/src/components/molecules/Content/Details/index.tsx, web/src/components/molecules/Content/Form/index.tsx, web/src/components/molecules/Content/types.ts, web/src/components/molecules/Content/utils.ts Added new VersionedItem type and versions: VersionedItem[] prop; updated UI to include a tabbed view for metadata and version history; refactored utility types.
web/src/components/molecules/Content/Form/SidebarWrapper.tsx Removed linked requests functionality by eliminating the onNavigateToRequest prop and associated UI components.
web/src/components/organisms/DataConverters/content.ts, web/src/gql/graphql-client-api.tsx, web/src/gql/queries/item.ts Introduced fromGraphQLversionsByItem function and new GraphQL query (VERSIONS_BY_ITEM) with associated query types and hooks to process versioned item data.
web/src/components/organisms/Project/Content/ContentDetails/hooks.ts, web/src/components/organisms/Project/Content/ContentDetails/index.tsx, web/src/components/organisms/Project/Content/ContentList/hooks.ts, web/src/components/organisms/Project/Content/hooks.ts Integrated version data by adding a new versions variable in hooks and components; updated mutation handlers to include additional refetch queries and revised dependency arrays.

Sequence Diagram(s)

sequenceDiagram
  participant CD as ContentDetails
  participant CH as Versions Hook
  participant GQL as GraphQL API
  participant CF as ContentForm
  participant UI as User

  CD->>CH: Request version data (via itemId)
  CH->>GQL: Execute VersionsByItemQuery
  GQL-->>CH: Return version data
  CH->>CH: Process data using fromGraphQLversionsByItem
  CH-->>CD: Provide versions
  CD->>CF: Pass versions prop to ContentForm
  UI->>CF: Select "History Version" Tab
  CF-->>UI: Render version history view
Loading

Possibly related PRs

  • fix(web): updating metadata from the table is rejected #1260: The changes in the main PR are related to the modifications in the ContentDetails component's hooks, specifically the introduction of new methods and variables for managing versioned items, which aligns with the overall theme of enhancing item management in the retrieved PR.
  • fix(web): warning occurs when re-referring to an item referenced earlier #1264: The changes in the main PR are related to the modifications in the Props type for the ContentDetailsMolecule component, specifically the addition of the onGetVersionedItem method, which aligns with the updates to the onCheckItemReference method in the retrieved PR that also modifies the Props type in the same component.
  • fix(web): handling initial values of multiple field #1345: The changes in the main PR are related to the introduction of new types and modifications to the Props type in the same file (types.ts) as the retrieved PR, which also adds new types (FormValue and FormGroupValue). Both PRs enhance type definitions for managing form-related data.

Suggested labels

server

Suggested reviewers

  • nourbalaha

Poem

I'm just a rabbit, hopping with delight,
A new VersionedItem made my code take flight,
Tabs for history and metadata gleam so bright,
Removed old requests, making everything feel light.
In a garden of code, I celebrate the new insight! 🐇💻

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the web label Feb 17, 2025
Copy link

netlify bot commented Feb 17, 2025

Deploy Preview for reearth-cms ready!

Name Link
🔨 Latest commit d135a17
🔍 Latest deploy log https://app.netlify.com/sites/reearth-cms/deploys/67b6ea1810b23200085cb7f6
😎 Deploy Preview https://deploy-preview-1409--reearth-cms.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
web/src/components/molecules/Content/types.ts (1)

97-105: Consider using a more specific type for the requests field.

The requests field type could be more specific to version-related request properties. Consider whether all request properties are needed in the version history context.

-  requests: Pick<Request, "id" | "title">[];
+  requests: Array<{
+    id: string;
+    title: string;
+  }>;

This approach:

  1. Makes the type more explicit about what data is expected
  2. Reduces coupling with the Request type
  3. Improves readability for developers
web/src/components/molecules/Content/Form/SidebarWrapper.tsx (1)

19-32: Consider simplifying the getStatusBadge function.

The function could be more concise while maintaining readability.

-  const getStatusBadge = (status: ItemStatus) => {
-    const itemStatus = status.split("_") as StateType[];
-    return (
-      <>
-        {itemStatus.map((state, index) => (
-          <StyledBadge
-            key={index}
-            color={stateColors[state]}
-            text={index === itemStatus.length - 1 ? t(state) : undefined}
-          />
-        ))}
-      </>
-    );
-  };
+  const getStatusBadge = (status: ItemStatus) => (
+    <>
+      {status.split("_").map((state, index, array) => (
+        <StyledBadge
+          key={state}
+          color={stateColors[state as StateType]}
+          text={index === array.length - 1 ? t(state) : undefined}
+        />
+      ))}
+    </>
+  );
web/src/components/organisms/Project/Content/hooks.ts (1)

61-63: LGTM! Consistent refetch behavior across mutations.

All mutations now properly refresh the necessary queries, ensuring data consistency.

Consider extracting the refetch queries array.

The same array of queries is repeated three times. Consider extracting it to a constant.

+const VERSION_RELATED_QUERIES = ["SearchItem", "GetItem", "VersionsByItem"] as const;
+
 const [updateRequest] = useUpdateRequestMutation({
-  refetchQueries: ["SearchItem", "GetItem", "VersionsByItem"],
+  refetchQueries: VERSION_RELATED_QUERIES,
 });

 const [publishItem, { loading: publishLoading }] = usePublishItemMutation({
-  refetchQueries: ["SearchItem", "GetItem", "VersionsByItem"],
+  refetchQueries: VERSION_RELATED_QUERIES,
 });

 const [unpublishItem, { loading: unpublishLoading }] = useUnpublishItemMutation({
-  refetchQueries: ["SearchItem", "GetItem", "VersionsByItem"],
+  refetchQueries: VERSION_RELATED_QUERIES,
 });

Also applies to: 97-99, 118-120

web/src/gql/queries/item.ts (1)

153-196: Consider optimizing the request items query.

The query fetches all request items and then filters them in the client-side converter function. Consider optimizing by:

  1. Adding arguments to filter requests at the GraphQL level.
  2. Using GraphQL fragments to reduce duplication of Integration/User type fields.

Example optimization:

 export const VERSIONS_BY_ITEM = gql`
+  fragment ActorFields on Actor {
+    ... on Integration {
+      name
+    }
+    ... on User {
+      name
+    }
+  }
+
   query VersionsByItem($itemId: ID!) {
     versionsByItem(itemId: $itemId) {
       version
       refs
       value {
         id
         modelId
         status
         createdAt
         updatedAt
-        createdBy {
-          ... on Integration {
-            name
-          }
-          ... on User {
-            name
-          }
-        }
-        updatedBy {
-          ... on Integration {
-            name
-          }
-          ... on User {
-            name
-          }
-        }
+        createdBy {
+          ...ActorFields
+        }
+        updatedBy {
+          ...ActorFields
+        }
         requests {
           id
           title
           items {
             itemId
             version
             item {
               value {
                 modelId
               }
             }
           }
         }
       }
     }
   }
 `;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b11b4c and 56dec2f.

📒 Files selected for processing (12)
  • web/src/components/molecules/Content/Details/index.tsx (4 hunks)
  • web/src/components/molecules/Content/Form/SidebarWrapper.tsx (1 hunks)
  • web/src/components/molecules/Content/Form/index.tsx (9 hunks)
  • web/src/components/molecules/Content/types.ts (2 hunks)
  • web/src/components/molecules/Content/utils.ts (1 hunks)
  • web/src/components/organisms/DataConverters/content.ts (3 hunks)
  • web/src/components/organisms/Project/Content/ContentDetails/hooks.ts (6 hunks)
  • web/src/components/organisms/Project/Content/ContentDetails/index.tsx (2 hunks)
  • web/src/components/organisms/Project/Content/ContentList/hooks.ts (1 hunks)
  • web/src/components/organisms/Project/Content/hooks.ts (3 hunks)
  • web/src/gql/graphql-client-api.tsx (2 hunks)
  • web/src/gql/queries/item.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: ci-web / ci
  • GitHub Check: Redirect rules - reearth-cms
  • GitHub Check: Header rules - reearth-cms
  • GitHub Check: Pages changed - reearth-cms
🔇 Additional comments (13)
web/src/components/molecules/Content/utils.ts (1)

4-4: LGTM! Good use of TypeScript's built-in utility type.

The change to use Record<StateType, ColorType> improves code readability while maintaining the same functionality.

web/src/components/molecules/Content/Form/SidebarWrapper.tsx (1)

12-14: LGTM! Clean removal of linked requests functionality.

The simplified Props type makes the component more focused and maintainable.

web/src/components/organisms/Project/Content/ContentDetails/index.tsx (1)

24-25: LGTM!

Clean implementation of prop drilling for the versions data. The changes follow React best practices.

Also applies to: 145-145

web/src/components/molecules/Content/Details/index.tsx (1)

9-14: LGTM!

Clean implementation of TypeScript types and prop definitions for the version history feature.

Also applies to: 39-39

web/src/components/organisms/Project/Content/ContentDetails/hooks.ts (3)

589-593: LGTM! Query implementation follows best practices.

The useVersionsByItemQuery implementation:

  • Uses appropriate fetch policy for real-time data.
  • Properly skips the query when itemId is not available.
  • Follows React Query patterns.

595-601: LGTM! Version data transformation is optimized.

The version data transformation:

  • Uses useMemo for performance optimization.
  • Reverses the array to show latest versions first.
  • Safely handles undefined data.

299-299: LGTM! Proper data consistency handling.

Adding "VersionsByItem" to refetchQueries ensures version data is refreshed after item updates.

web/src/components/organisms/Project/Content/ContentList/hooks.ts (1)

607-613: Verify if removing refetch from dependency array is intentional.

The refetch function has been removed from the dependency array of handleBulkAddItemToRequest. While this might prevent unnecessary re-renders, please ensure that:

  1. The function doesn't need to react to changes in the refetch function.
  2. The data is being refreshed through other means (e.g., through the mutation's refetchQueries).
web/src/components/molecules/Content/Form/index.tsx (2)

748-802: LGTM! Clean and organized tab implementation.

The tab implementation:

  • Clearly separates metadata and version history.
  • Uses conditional rendering for version history tab.
  • Follows consistent design patterns.

894-950: LGTM! Well-structured styled components.

The styled components:

  • Follow a clean and focused approach.
  • Use semantic naming.
  • Implement proper styling for version history UI.
web/src/gql/graphql-client-api.tsx (3)

2173-2178: LGTM! Well-structured type definition for versioned items.

The VersionedItem type is well-designed with appropriate fields for version tracking, including version string, references array, optional parents array, and the actual item value.


2573-2575: LGTM! Clean and focused query variables type.

The VersionsByItemQueryVariables type is appropriately defined with just the required itemId parameter.


2827-2903: LGTM! Comprehensive query implementation for version history.

The VersionsByItemQuery is well-implemented with:

  • Complete version metadata (version, refs, parents)
  • Detailed item information including status, timestamps, and user data
  • Proper nesting of related request information

createdAt: version.value.createdAt,
updatedAt: version.value.updatedAt,
createdBy: { name: version.value.createdBy?.name ?? "" },
updatedBy: { name: version.value.createdBy?.name ?? "" },
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect property access for updatedBy name.

The updatedBy.name is incorrectly using createdBy instead of updatedBy.

Apply this diff to fix the property access:

-      updatedBy: { name: version.value.createdBy?.name ?? "" },
+      updatedBy: { name: version.value.updatedBy?.name ?? "" },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
updatedBy: { name: version.value.createdBy?.name ?? "" },
updatedBy: { name: version.value.updatedBy?.name ?? "" },

Comment on lines 150 to 190
export const fromGraphQLversionsByItem = (
GQLVersionsByItem: GQLVersionedItem[],
): VersionedItem[] => {
let publicIndex = -1;
const result = GQLVersionsByItem.map<VersionedItem>((version, index) => {
let status: StateType = "DRAFT";
if (version.refs.includes("public")) {
status = "PUBLIC";
publicIndex = index;
}
return {
version: version.version,
status,
createdAt: version.value.createdAt,
updatedAt: version.value.updatedAt,
createdBy: { name: version.value.createdBy?.name ?? "" },
updatedBy: { name: version.value.createdBy?.name ?? "" },
requests:
version.value.requests
?.filter(request =>
request.items.some(
item =>
item.item?.value.modelId === version.value.modelId &&
item.itemId === version.value.id &&
item.version === version.version,
),
)
.map(request => ({
id: request.id,
title: request.title,
})) ?? [],
};
}).map((version, index) => {
if (index > publicIndex && version.requests.length) {
version.status = "REVIEW";
}
return version;
});

return result;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for undefined or null values.

The function should handle potential undefined or null values from the GraphQL response to prevent runtime errors.

Apply this diff to add error handling:

 export const fromGraphQLversionsByItem = (
   GQLVersionsByItem: GQLVersionedItem[],
 ): VersionedItem[] => {
+  if (!GQLVersionsByItem?.length) return [];
   let publicIndex = -1;
   const result = GQLVersionsByItem.map<VersionedItem>((version, index) => {
+    if (!version?.value) {
+      throw new Error("Invalid version data");
+    }
     let status: StateType = "DRAFT";
-    if (version.refs.includes("public")) {
+    if (version.refs?.includes("public")) {
       status = "PUBLIC";
       publicIndex = index;
     }
     return {
       version: version.version,
       status,
       createdAt: version.value.createdAt,
       updatedAt: version.value.updatedAt,
       createdBy: { name: version.value.createdBy?.name ?? "" },
-      updatedBy: { name: version.value.createdBy?.name ?? "" },
+      updatedBy: { name: version.value.updatedBy?.name ?? "" },
       requests:
         version.value.requests
           ?.filter(request =>
-            request.items.some(
+            request?.items?.some(
               item =>
                 item.item?.value.modelId === version.value.modelId &&
                 item.itemId === version.value.id &&
                 item.version === version.version,
             ),
           )
           .map(request => ({
             id: request.id,
             title: request.title,
           })) ?? [],
     };
   }).map((version, index) => {
     if (index > publicIndex && version.requests.length) {
       version.status = "REVIEW";
     }
     return version;
   });

   return result;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const fromGraphQLversionsByItem = (
GQLVersionsByItem: GQLVersionedItem[],
): VersionedItem[] => {
let publicIndex = -1;
const result = GQLVersionsByItem.map<VersionedItem>((version, index) => {
let status: StateType = "DRAFT";
if (version.refs.includes("public")) {
status = "PUBLIC";
publicIndex = index;
}
return {
version: version.version,
status,
createdAt: version.value.createdAt,
updatedAt: version.value.updatedAt,
createdBy: { name: version.value.createdBy?.name ?? "" },
updatedBy: { name: version.value.createdBy?.name ?? "" },
requests:
version.value.requests
?.filter(request =>
request.items.some(
item =>
item.item?.value.modelId === version.value.modelId &&
item.itemId === version.value.id &&
item.version === version.version,
),
)
.map(request => ({
id: request.id,
title: request.title,
})) ?? [],
};
}).map((version, index) => {
if (index > publicIndex && version.requests.length) {
version.status = "REVIEW";
}
return version;
});
return result;
};
export const fromGraphQLversionsByItem = (
GQLVersionsByItem: GQLVersionedItem[],
): VersionedItem[] => {
if (!GQLVersionsByItem?.length) return [];
let publicIndex = -1;
const result = GQLVersionsByItem.map<VersionedItem>((version, index) => {
if (!version?.value) {
throw new Error("Invalid version data");
}
let status: StateType = "DRAFT";
if (version.refs?.includes("public")) {
status = "PUBLIC";
publicIndex = index;
}
return {
version: version.version,
status,
createdAt: version.value.createdAt,
updatedAt: version.value.updatedAt,
createdBy: { name: version.value.createdBy?.name ?? "" },
updatedBy: { name: version.value.updatedBy?.name ?? "" },
requests:
version.value.requests
?.filter(request =>
request?.items?.some(
item =>
item.item?.value.modelId === version.value.modelId &&
item.itemId === version.value.id &&
item.version === version.version,
),
)
.map(request => ({
id: request.id,
title: request.title,
})) ?? [],
};
}).map((version, index) => {
if (index > publicIndex && version.requests.length) {
version.status = "REVIEW";
}
return version;
});
return result;
};

const { name } = isLast ? version.createdBy : version.updatedBy;
return (
<HistoryCard key={version.version}>
<HistoryTitle onClick={() => console.log(version.version)}>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement the version click handler.

The click handler for version history is currently just logging the version. Implement the actual functionality to handle version selection.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
web/src/gql/provider.tsx (1)

43-49: LGTM! Good approach to cache management for versioned items.

The composite key using id and version is a good choice for caching versioned items. This ensures that different versions of the same item are properly cached and managed.

Consider adding a comment explaining the composite key strategy for future maintainers:

 const cache = new InMemoryCache({
   typePolicies: {
     Item: {
+      // Using composite key (id + version) to uniquely identify different versions of the same item
       keyFields: obj => `${obj.id}-${obj.version}`,
     },
   },
 });
web/src/components/organisms/DataConverters/content.ts (1)

183-186: Simplify status update logic.

The status update logic can be simplified by checking against the public index directly in the first map operation, avoiding the need for a second map.

Apply this diff to simplify the logic:

-  }).map((version, index) => {
-    if (index !== publicIndex && version.requests.length) {
-      version.status = "REVIEW";
-    }
-    return version;
-  });
+    status: index !== publicIndex && version.value.requests?.some(
+      request =>
+        request.state === "WAITING" &&
+        request.items.some(
+          item =>
+            item.item?.value.modelId === version.value.modelId &&
+            item.itemId === version.value.id &&
+            item.version === version.version,
+        ),
+    )
+      ? "REVIEW"
+      : status,
web/src/components/molecules/Content/Form/index.tsx (2)

593-595: Consider initializing activeKey for better UX.

The activeKey state is not initialized, which might lead to an empty tab selection on initial render.

Initialize the active tab to "meta" for better user experience:

- const [activeKey, setActiveKey] = useState<string>();
+ const [activeKey, setActiveKey] = useState<string>("meta");

744-776: Add empty state for version history.

The version history tab should display a message when no versions are available.

Add an empty state component:

  {versions.length && (
    <TabPane tab={t("Version History")} key="history">
      <History>
+       {versions.length === 0 ? (
+         <EmptyState
+           description={t("No version history available")}
+           icon={<Icon icon="history" />}
+         />
+       ) : (
        {versions.map((version, index) => {
          // ... existing code ...
        })}
+       )}
      </History>
    </TabPane>
  )}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1766ca and fdfc2f6.

📒 Files selected for processing (22)
  • web/src/components/atoms/PageHeader/index.tsx (0 hunks)
  • web/src/components/molecules/Common/Form/GroupItem/index.tsx (3 hunks)
  • web/src/components/molecules/Common/MultiValueField/MultiValueGroup/index.tsx (3 hunks)
  • web/src/components/molecules/Content/Details/index.tsx (7 hunks)
  • web/src/components/molecules/Content/Form/FormItemsWrapper.tsx (1 hunks)
  • web/src/components/molecules/Content/Form/ReferenceFormItem/index.tsx (3 hunks)
  • web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/AssetField.tsx (2 hunks)
  • web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/GroupField.tsx (4 hunks)
  • web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/ReferenceField.tsx (3 hunks)
  • web/src/components/molecules/Content/Form/index.tsx (14 hunks)
  • web/src/components/molecules/Content/types.ts (2 hunks)
  • web/src/components/molecules/Integration/IntegrationTable/index.tsx (1 hunks)
  • web/src/components/molecules/Member/MemberTable/index.tsx (1 hunks)
  • web/src/components/molecules/Request/Details/ItemForm.tsx (3 hunks)
  • web/src/components/molecules/Request/Details/Request.tsx (1 hunks)
  • web/src/components/molecules/Schema/index.tsx (1 hunks)
  • web/src/components/organisms/DataConverters/content.ts (3 hunks)
  • web/src/components/organisms/Project/Content/ContentDetails/hooks.ts (10 hunks)
  • web/src/components/organisms/Project/Content/ContentDetails/index.tsx (4 hunks)
  • web/src/gql/graphql-client-api.tsx (2 hunks)
  • web/src/gql/provider.tsx (1 hunks)
  • web/src/gql/queries/item.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • web/src/components/atoms/PageHeader/index.tsx
✅ Files skipped from review due to trivial changes (4)
  • web/src/components/molecules/Schema/index.tsx
  • web/src/components/molecules/Request/Details/Request.tsx
  • web/src/components/molecules/Member/MemberTable/index.tsx
  • web/src/components/molecules/Integration/IntegrationTable/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/src/gql/queries/item.ts
  • web/src/components/organisms/Project/Content/ContentDetails/index.tsx
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: ci-web / ci
  • GitHub Check: Redirect rules - reearth-cms
  • GitHub Check: Header rules - reearth-cms
  • GitHub Check: Pages changed - reearth-cms
🔇 Additional comments (18)
web/src/components/molecules/Content/types.ts (2)

10-10: LGTM! Clear type definition for form values.

The FormValues type correctly represents the structure of form data, allowing for both simple values and nested group values.


99-105: LGTM! Well-structured type for version history.

The VersionedItem type includes all necessary fields for tracking version history:

  • version identifier
  • status tracking
  • timestamp for version creation
  • creator information
  • associated requests
web/src/components/molecules/Request/Details/ItemForm.tsx (2)

16-16: LGTM! Good improvement to type safety.

Making referencedItems a required prop ensures that the component always has the necessary data to function properly.


42-43: LGTM! Consistent prop naming across components.

The standardization of the referencedItems prop name across ReferenceField and GroupField components improves code consistency and maintainability.

Also applies to: 52-53

web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/ReferenceField.tsx (1)

10-11: LGTM! Clean prop structure update.

The replacement of formItemsData with referencedItems maintains consistency with other components while preserving the component's functionality.

web/src/components/molecules/Content/Form/FormItemsWrapper.tsx (1)

1-127: LGTM! Well-structured component with good separation of concerns.

The component is well-organized, handles different field types appropriately, and follows React best practices. The props interface is comprehensive and the styled components provide good layout management.

web/src/components/molecules/Content/Form/ReferenceFormItem/index.tsx (1)

15-15: LGTM! Consistent prop naming across components.

The transition from formItemsData to referencedItems aligns with similar changes in related components, maintaining consistency throughout the codebase.

Also applies to: 81-85

web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/AssetField.tsx (1)

16-17: LGTM! Type-safe property additions.

The addition of onGetAsset property and repositioning of disabled property maintain type safety while enhancing the component's functionality.

web/src/components/organisms/DataConverters/content.ts (1)

150-190: Add error handling for undefined or null values.

The function should handle potential undefined or null values from the GraphQL response to prevent runtime errors.

web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/GroupField.tsx (1)

18-18: LGTM! Consistent prop renaming.

The renaming of formItemsData to referencedItems is consistent across the component and its children. The prop is now required, which helps ensure data consistency.

Also applies to: 64-64, 112-112, 153-153

web/src/components/molecules/Common/MultiValueField/MultiValueGroup/index.tsx (1)

29-29: LGTM! Consistent prop renaming.

The renaming of formItemsData to referencedItems is consistent across the component and its children. The prop is now required, which helps ensure data consistency.

Also applies to: 78-78, 195-195

web/src/components/molecules/Common/Form/GroupItem/index.tsx (1)

25-25: LGTM! Consistent prop renaming.

The renaming of formItemsData to referencedItems is consistent across the component and its children. The prop is now required, which helps ensure data consistency.

Also applies to: 77-77, 220-220

web/src/components/molecules/Content/Details/index.tsx (1)

9-15: LGTM! Well-structured version history support.

The changes effectively introduce version history support:

  1. New types VersionedItem and FormValues are imported.
  2. New props versions and onGetVersionedItem are added.
  3. Props are correctly passed down to ContentForm.

The async nature of onGetVersionedItem is appropriate for fetching versioned items.

Also applies to: 40-40, 73-73, 137-137, 174-174, 253-253, 265-265

web/src/components/organisms/Project/Content/ContentDetails/hooks.ts (2)

596-600: Verify error handling in version query.

The version query implementation should handle potential errors to ensure robust version management.

Add error handling to prevent potential issues:

  const { data: versionsData } = useVersionsByItemQuery({
    fetchPolicy: "cache-and-network",
    variables: { itemId: itemId ?? "" },
    skip: !itemId,
+   onError: error => {
+     Notification.error({ message: t("Failed to fetch versions.") });
+     console.error("Version query error:", error);
+   },
  });

299-301: LGTM! Proper refetch queries configuration.

The mutation handlers correctly include "VersionsByItem" in their refetch queries to ensure version data stays in sync.

Also applies to: 520-522

web/src/gql/graphql-client-api.tsx (3)

2573-2578: LGTM! New version query variables type added.

The new VersionsByItemQueryVariables type is correctly defined with the required itemId parameter of type ID.


2827-2871: LGTM! New version query definition added.

The VersionsByItemQuery is well-structured and includes all necessary fields for version history:

  • Version string
  • References array
  • Item details including ID, version, model ID, status, timestamps
  • Creator and updater information
  • Associated requests

4890-4905: LGTM! Query hooks generated correctly.

The Apollo hooks for the versions query are properly generated:

  • useVersionsByItemQuery for standard query
  • useVersionsByItemLazyQuery for lazy loading
  • useVersionsByItemSuspenseQuery for suspense support

Comment on lines 614 to 625
const handleGetVersionedItem = useCallback(
async (id: string, version: string) => {
const res = await getItem({
variables: {
id,
// version,
},
});
return await initialValueGet(fromGraphQLItem(res.data?.node as GQLItem));
},
[getItem, initialValueGet],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix commented out version parameter in handleGetVersionedItem.

The version parameter is commented out in the getItem query, which means version history functionality might not work as expected.

Uncomment the version parameter to ensure proper version retrieval:

  const handleGetVersionedItem = useCallback(
    async (id: string, version: string) => {
      const res = await getItem({
        variables: {
          id,
-         // version,
+         version,
        },
      });
      return await initialValueGet(fromGraphQLItem(res.data?.node as GQLItem));
    },
    [getItem, initialValueGet],
  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleGetVersionedItem = useCallback(
async (id: string, version: string) => {
const res = await getItem({
variables: {
id,
// version,
},
});
return await initialValueGet(fromGraphQLItem(res.data?.node as GQLItem));
},
[getItem, initialValueGet],
);
const handleGetVersionedItem = useCallback(
async (id: string, version: string) => {
const res = await getItem({
variables: {
id,
version,
},
});
return await initialValueGet(fromGraphQLItem(res.data?.node as GQLItem));
},
[getItem, initialValueGet],
);

Comment on lines 610 to 612
const handleRestore = useCallback(() => {
form.setFieldsValue(versionForm.getFieldsValue());
}, [form, versionForm]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add confirmation dialog for version restoration.

The version restoration process should prompt for confirmation to prevent accidental data loss.

Add a confirmation dialog:

  const handleRestore = useCallback(() => {
+   Modal.confirm({
+     title: t("Restore version"),
+     content: t("Are you sure you want to restore this version? This will overwrite your current changes."),
+     onOk: () => {
      form.setFieldsValue(versionForm.getFieldsValue());
+     },
+   });
  }, [form, versionForm]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleRestore = useCallback(() => {
form.setFieldsValue(versionForm.getFieldsValue());
}, [form, versionForm]);
const handleRestore = useCallback(() => {
Modal.confirm({
title: t("Restore version"),
content: t("Are you sure you want to restore this version? This will overwrite your current changes."),
onOk: () => {
form.setFieldsValue(versionForm.getFieldsValue());
},
});
}, [form, versionForm]);

Comment on lines 596 to 604
const versionClick = useCallback(
async (versionedItem: VersionedItem) => {
if (versionedItem.version === item?.version || !item?.id) return;
const res = await onGetVersionedItem(item.id, versionedItem.version);
versionForm.setFieldsValue(res);
setVersionedItem(versionedItem);
},
[item?.id, item?.version, onGetVersionedItem, versionForm],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add loading state for version retrieval.

The version click handler should show a loading state while fetching the versioned item data.

Add loading state to improve user feedback:

+ const [versionLoading, setVersionLoading] = useState(false);

  const versionClick = useCallback(
    async (versionedItem: VersionedItem) => {
      if (versionedItem.version === item?.version || !item?.id) return;
+     setVersionLoading(true);
      try {
        const res = await onGetVersionedItem(item.id, versionedItem.version);
        versionForm.setFieldsValue(res);
        setVersionedItem(versionedItem);
+     } catch (error) {
+       Notification.error({ message: t("Failed to fetch version.") });
+     } finally {
+       setVersionLoading(false);
+     }
    },
    [item?.id, item?.version, onGetVersionedItem, versionForm],
  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const versionClick = useCallback(
async (versionedItem: VersionedItem) => {
if (versionedItem.version === item?.version || !item?.id) return;
const res = await onGetVersionedItem(item.id, versionedItem.version);
versionForm.setFieldsValue(res);
setVersionedItem(versionedItem);
},
[item?.id, item?.version, onGetVersionedItem, versionForm],
);
const [versionLoading, setVersionLoading] = useState(false);
const versionClick = useCallback(
async (versionedItem: VersionedItem) => {
if (versionedItem.version === item?.version || !item?.id) return;
setVersionLoading(true);
try {
const res = await onGetVersionedItem(item.id, versionedItem.version);
versionForm.setFieldsValue(res);
setVersionedItem(versionedItem);
} catch (error) {
Notification.error({ message: t("Failed to fetch version.") });
} finally {
setVersionLoading(false);
}
},
[item?.id, item?.version, onGetVersionedItem, versionForm],
);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
web/src/components/organisms/DataConverters/content.ts (1)

150-199: ⚠️ Potential issue

Add error handling for undefined or null values.

The function should handle potential undefined or null values from the GraphQL response to prevent runtime errors.

Apply this diff to add error handling:

 export const fromGraphQLversionsByItem = (
   GQLVersionsByItem: GQLVersionedItem[],
 ): VersionedItem[] => {
+  if (!GQLVersionsByItem?.length) return [];
   let publicIndex = -1;
   const result = GQLVersionsByItem.map<VersionedItem>((version, index) => {
+    if (!version?.value) {
+      throw new Error("Invalid version data");
+    }
     let status: StateType = "DRAFT";
-    if (version.refs.includes("public")) {
+    if (version.refs?.includes("public")) {
       status = "PUBLIC";
       publicIndex = index;
     }
     return {
       version: version.version,
       status,
       timestamp: version.value.updatedAt ?? version.value.createdAt,
       creator: { name: version.value.updatedBy?.name ?? version.value.createdBy?.name ?? "" },
       fields: version.value.fields.map(
         field =>
           ({
             schemaFieldId: field.schemaFieldId,
             itemGroupId: field.itemGroupId,
             type: field.type,
             value: field.value,
           }) as ItemField,
       ),
       requests:
         version.value.requests
           ?.filter(
             request =>
+              request?.state === "WAITING" &&
               request?.items?.some(
                 item =>
                   item.item?.value.modelId === version.value.modelId &&
                   item.itemId === version.value.id &&
                   item.version === version.version,
               ),
           )
           .map(request => ({
             id: request.id,
             title: request.title,
           })) ?? [],
     };
   });

   return result;
 };
web/src/components/molecules/Content/Form/index.tsx (2)

610-612: 🛠️ Refactor suggestion

Add confirmation dialog for version restoration.

The restore operation should prompt for confirmation as it overwrites the current form state.

  const handleRestore = useCallback(() => {
+   Modal.confirm({
+     title: t("Restore version"),
+     content: t("Are you sure you want to restore this version? This will overwrite your current changes."),
+     onOk: () => {
      form.setFieldsValue(versionForm.getFieldsValue());
+     },
+   });
  }, [form, versionForm]);

596-604: 🛠️ Refactor suggestion

Add error handling and loading state to version management.

The version click handler should handle errors and show loading state while fetching version data.

+ const [versionLoading, setVersionLoading] = useState(false);

  const versionClick = useCallback(
    async (versionedItem: VersionedItem) => {
      if (versionedItem.version === item?.version || !item?.id) return;
+     setVersionLoading(true);
      try {
        const res = await onGetVersionedItem(versionedItem.version);
        versionForm.setFieldsValue(res);
        setVersionedItem(versionedItem);
+     } catch (error) {
+       Notification.error({ message: t("Failed to fetch version data") });
+     } finally {
+       setVersionLoading(false);
+     }
    },
    [item?.id, item?.version, onGetVersionedItem, versionForm],
  );
🧹 Nitpick comments (3)
web/src/components/molecules/Content/Form/index.tsx (3)

65-65: Add type validation for version management props.

Consider adding validation for the version parameter in onGetVersionedItem to ensure it's a valid version string.

-  versions: VersionedItem[];
-  onGetVersionedItem: (version: string) => Promise<FormValues>;
+  versions: VersionedItem[];
+  onGetVersionedItem: (version: NonEmptyString) => Promise<FormValues>;

+ type NonEmptyString = string & { __brand: 'NonEmptyString' };

Also applies to: 118-118


593-595: Initialize the active tab state.

The activeKey state is not initialized, which could lead to undefined behavior. Consider setting a default tab.

- const [activeKey, setActiveKey] = useState<string>();
+ const [activeKey, setActiveKey] = useState<string>("meta");

750-755: Improve accessibility of version history items.

Add ARIA labels and keyboard navigation support for better accessibility.

- <HistoryTitle onClick={() => versionClick(version)}>
+ <HistoryTitle
+   onClick={() => versionClick(version)}
+   onKeyPress={(e) => e.key === 'Enter' && versionClick(version)}
+   role="button"
+   tabIndex={0}
+   aria-label={t("View version from {{date}}", { date: dateTimeFormat(version.timestamp, "YYYY/MM/DD, HH:mm") })}>
    <Tooltip title={t(version.status)}>
      <Badge color={stateColors[version.status]} />
    </Tooltip>
    {dateTimeFormat(version.timestamp, "YYYY/MM/DD, HH:mm")}
  </HistoryTitle>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdfc2f6 and c5ce810.

📒 Files selected for processing (7)
  • web/src/components/molecules/Content/Details/index.tsx (7 hunks)
  • web/src/components/molecules/Content/Form/index.tsx (14 hunks)
  • web/src/components/molecules/Content/types.ts (2 hunks)
  • web/src/components/organisms/DataConverters/content.ts (3 hunks)
  • web/src/components/organisms/Project/Content/ContentDetails/hooks.ts (10 hunks)
  • web/src/gql/graphql-client-api.tsx (2 hunks)
  • web/src/gql/queries/item.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/gql/queries/item.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: ci-web / ci
🔇 Additional comments (12)
web/src/components/molecules/Content/types.ts (2)

10-10: LGTM! Type definition enhances form data type safety.

The FormValues type provides a clear and type-safe way to handle form data by leveraging existing types.


99-106: LGTM! Well-structured version history type.

The VersionedItem type effectively captures version metadata and content with:

  • Version identifier and status tracking
  • Timestamp for version history
  • Creator information
  • Fields and associated requests
web/src/components/organisms/DataConverters/content.ts (1)

299-299: LGTM! Proper query refetching.

The addition of "VersionsByItem" to refetchQueries ensures version data stays in sync after mutations.

Also applies to: 520-520

web/src/components/molecules/Content/Details/index.tsx (2)

40-40: LGTM! Well-typed version history props.

The new props enhance the component with version history capabilities:

  • versions array for storing version history
  • onGetVersionedItem callback for retrieving specific versions

Also applies to: 73-73


137-137: LGTM! Proper prop forwarding.

The version-related props are correctly passed down to the ContentForm component, maintaining the data flow.

Also applies to: 174-174, 253-253, 265-265

web/src/components/organisms/Project/Content/ContentDetails/hooks.ts (3)

595-599: LGTM! Well-configured version query.

The version query is properly configured with:

  • Cache-and-network fetch policy for optimal data freshness
  • Skip condition to prevent unnecessary queries

601-607: LGTM! Efficient version data processing.

The version data processing is optimized with:

  • Memoization to prevent unnecessary recalculations
  • Reverse sorting for chronological display

417-463: LGTM! Well-refactored initialization logic.

The initialValueGet function is well-structured with:

  • Async/await for better error handling
  • Support for both new and existing items
  • Clear separation of group and field handling
web/src/components/molecules/Content/Form/index.tsx (1)

1-920: Well-structured implementation of version history feature!

The implementation effectively manages version history with proper separation of concerns, state management, and UI components. The code is clean and maintainable.

web/src/gql/graphql-client-api.tsx (3)

2573-2578: LGTM! Query variables type is well-defined.

The VersionsByItemQueryVariables type properly enforces the required itemId parameter with correct typing.


4827-4877: LGTM! Query structure is comprehensive and well-typed.

The VersionsByItem query is well-structured with:

  • Complete version metadata (version string, refs)
  • Nested item data with all relevant fields
  • Associated request information
  • Proper field selections for the version history UI

4896-4911: LGTM! Generated hooks follow Apollo best practices.

The generated hooks provide:

  • Standard query hook with loading/error states
  • Lazy query variant for manual execution
  • Suspense query variant for React Suspense support
  • Proper TypeScript type safety throughout

Comment on lines 609 to 617
const handleGetVersionedItem = useCallback(
(version: string) => {
const res = versions.find(v => v.version === version);
if (res) {
return initialValueGet(res.fields);
}
},
[initialValueGet, versions],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for version not found.

The handleGetVersionedItem function should handle the case when a version is not found.

Apply this diff to add error handling:

 const handleGetVersionedItem = useCallback(
   (version: string) => {
     const res = versions.find(v => v.version === version);
     if (res) {
       return initialValueGet(res.fields);
+    } else {
+      throw new Error(`Version ${version} not found`);
     }
   },
   [initialValueGet, versions],
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleGetVersionedItem = useCallback(
(version: string) => {
const res = versions.find(v => v.version === version);
if (res) {
return initialValueGet(res.fields);
}
},
[initialValueGet, versions],
);
const handleGetVersionedItem = useCallback(
(version: string) => {
const res = versions.find(v => v.version === version);
if (res) {
return initialValueGet(res.fields);
} else {
throw new Error(`Version ${version} not found`);
}
},
[initialValueGet, versions]
);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
web/src/components/molecules/Content/Form/index.tsx (2)

611-615: 🛠️ Refactor suggestion

Add confirmation dialog for version restoration.

Version restoration should prompt for confirmation to prevent accidental data loss.

  const handleRestore = useCallback(() => {
+   Modal.confirm({
+     title: t("Restore version"),
+     content: t("Are you sure you want to restore this version? This will overwrite your current changes."),
+     onOk: () => {
      const values = versionForm.getFieldsValue();
      form.setFieldsValue(values);
      handleValuesChange(values);
+     },
+   });
  }, [form, handleValuesChange, versionForm, t]);

595-605: 🛠️ Refactor suggestion

Add loading state and error handling to version click handler.

The version click handler should provide feedback during loading and handle errors gracefully.

  const [activeKey, setActiveKey] = useState<string>();
  const [versionedItem, setVersionedItem] = useState<VersionedItem>();
+ const [versionLoading, setVersionLoading] = useState(false);

  const versionClick = useCallback(
    async (versionedItem: VersionedItem) => {
      if (versionedItem.version === item?.version) return;
+     setVersionLoading(true);
      try {
        const res = await onGetVersionedItem(versionedItem.version);
        versionForm.setFieldsValue(res);
        setVersionedItem(versionedItem);
+     } catch (error) {
+       Notification.error({ message: t("Failed to fetch version") });
+     } finally {
+       setVersionLoading(false);
+     }
    },
    [onGetVersionedItem, versionForm, item?.version, t],
  );
🧹 Nitpick comments (4)
web/src/components/organisms/Project/Content/ContentDetails/hooks.ts (3)

418-464: Consider memoizing groupInitialValuesUpdate.

The groupInitialValuesUpdate function is recreated on every render since it's defined inside initialValueGet. Consider extracting and memoizing it to optimize performance.

+const groupInitialValuesUpdate = useCallback((group: Group, itemGroupId: string, initialValues: FormValues) => {
+  group?.schema?.fields?.forEach(field => {
+    initialValues[field.id] = {
+      ...(initialValues[field.id] as FormGroupValue),
+      ...{ [itemGroupId]: valueGet(field) },
+    };
+  });
+}, [valueGet]);

 const initialValueGet = useCallback(
   async (fields?: ItemField[]) => {
     const initialValues: FormValues = {};
-    const groupInitialValuesUpdate = (group: Group, itemGroupId: string) => {
-      group?.schema?.fields?.forEach(field => {
-        initialValues[field.id] = {
-          ...(initialValues[field.id] as FormGroupValue),
-          ...{ [itemGroupId]: valueGet(field) },
-        };
-      });
-    };

596-608: Consider adding loading state for versions.

The versions query doesn't expose its loading state, which could lead to UI inconsistencies during data fetching.

-const { data: versionsData } = useVersionsByItemQuery({
+const { data: versionsData, loading: versionsLoading } = useVersionsByItemQuery({
   fetchPolicy: "cache-and-network",
   variables: { itemId: itemId ?? "" },
   skip: !itemId,
 });

 const versions = useMemo(
   () =>
     versionsData
       ? fromGraphQLversionsByItem(versionsData.versionsByItem as GQLVersionedItem[]).reverse()
       : [],
   [versionsData],
 );

+// Add versionsLoading to the return object
 return {
   // ...other properties
   versions,
+  versionsLoading,
 };

466-472: Consider adding error boundary for initialValuesSet.

The handleInitialValuesSet function could potentially throw if initialValueGet fails, but there's no error handling.

 useEffect(() => {
   if (itemLoading) return;
   const handleInitialValuesSet = async () => {
+    try {
       setInitialFormValues(await initialValueGet(currentItem?.fields ?? undefined));
+    } catch (error) {
+      console.error('Failed to set initial values:', error);
+      // Consider showing an error notification to the user
+      Notification.error({ message: t("Failed to load item data.") });
+    }
   };
   handleInitialValuesSet();
 }, [currentItem, initialValueGet, itemLoading]);
web/src/components/molecules/Content/Form/index.tsx (1)

747-779: Enhance accessibility of version history UI.

The version history UI is well-structured but could benefit from accessibility improvements.

  <TabPane tab={t("Version History")} key="history">
    <History>
      {versions.map((version, index) => {
        return (
-         <HistoryCard key={version.version}>
+         <HistoryCard 
+           key={version.version}
+           role="listitem"
+           aria-label={t("Version from {{date}}", { date: dateTimeFormat(version.timestamp, "YYYY/MM/DD, HH:mm") })}
+         >
-           <HistoryTitle onClick={() => versionClick(version)}>
+           <HistoryTitle
+             onClick={() => versionClick(version)}
+             role="button"
+             tabIndex={0}
+             onKeyPress={(e) => e.key === 'Enter' && versionClick(version)}
+           >
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5ce810 and d135a17.

📒 Files selected for processing (2)
  • web/src/components/molecules/Content/Form/index.tsx (15 hunks)
  • web/src/components/organisms/Project/Content/ContentDetails/hooks.ts (11 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: ci-web / ci
  • GitHub Check: Redirect rules - reearth-cms
  • GitHub Check: Header rules - reearth-cms
  • GitHub Check: Pages changed - reearth-cms
🔇 Additional comments (6)
web/src/components/organisms/Project/Content/ContentDetails/hooks.ts (3)

7-7: LGTM! Clean import organization.

The new imports are well-organized and properly grouped with related imports.

Also applies to: 20-22, 30-30, 44-44


299-301: LGTM! Proper query refetching.

The mutation correctly refetches both "GetItem" and "VersionsByItem" queries to ensure UI consistency after updates.


610-616: Add error handling for version not found.

The handleGetVersionedItem function should handle the case when a version is not found.

web/src/components/molecules/Content/Form/index.tsx (3)

6-16: LGTM: Props and imports are well-structured.

The new imports and props are properly organized and align well with the version history feature requirements.

Also applies to: 65-65, 118-118


277-299: LGTM: Form change handling is robust.

The implementation properly handles form changes, including complex group field comparisons and empty value conversions.


822-921: LGTM: Styled components are well-structured.

The styling implementation is clean, modular, and follows best practices for component organization.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant