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

fix(web): validate values and avoid redundant queries when updating metadata from table #1338

Merged
merged 12 commits into from
Dec 13, 2024

Conversation

caichi-t
Copy link
Contributor

@caichi-t caichi-t commented Dec 5, 2024

Overview

This PR fixes to validate values and avoid redundant queries when updating metadata from table.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced error handling for required fields and maximum length constraints in metadata updates.
    • Improved notification management during user interactions.
    • New translations added for error messages in English and Japanese.
  • Bug Fixes

    • Notifications are now consistently closed after relevant actions, improving test reliability.
  • Documentation

    • Updated English and Japanese translation files with new error messages and improved clarity for user prompts.

These updates aim to enhance user experience by providing clearer feedback and ensuring data integrity during interactions.

@caichi-t caichi-t requested a review from nourbalaha as a code owner December 5, 2024 05:52
Copy link

coderabbitai bot commented Dec 5, 2024

Walkthrough

The pull request introduces significant modifications to various components, primarily focusing on enhancing notification handling and validation logic. The closeNotification function now uses attribute verification instead of text checks, while tests for tag metadata and updates have been adjusted to ensure notifications are appropriately closed and metadata states are correctly asserted. Additionally, the ItemFormat component has been improved for error handling during text and URL input. Translation files have been updated to include new error messages and refine existing ones for clarity.

Changes

File Path Change Summary
web/e2e/common/notification.ts Updated closeNotification function to use aria-label attribute checks instead of text checks, added assertion to ensure alert is hidden after closure.
web/e2e/project/item/metadata/tag.spec.ts Added notification closure after clicking "close-circle" icon, removed line clicking on "tag1", ensuring consistent notification management in tests.
web/e2e/project/item/metadata/update.spec.ts Modified tests to include closeNotification calls, adjusted assertions for boolean attributes after metadata updates, confirming successful state changes.
web/src/components/molecules/Content/RenderField/ItemFormat.tsx Added handleTextBlur and modified handleUrlBlur for better input handling, incorporated error notifications for invalid URLs, adjusted rendering logic.
web/src/components/molecules/Content/RenderField/index.tsx Simplified conditional check for rendering ItemFormat component when value is "-", streamlining rendering logic.
web/src/components/organisms/Project/Content/ContentList/hooks.ts Enhanced metadata update logic with validation checks for required fields and maximum lengths, added error handling and notifications for validation failures.
web/src/i18n/translations/en.yml Added new error messages for required fields and maximum lengths, updated existing success and failure messages for clarity.
web/src/i18n/translations/ja.yml Added new translation entries for error messages, modified existing entries for consistency and clarity in success messages.

Possibly related PRs

Suggested reviewers

  • nourbalaha

Poem

🐇 In fields of code, where bunnies play,
Notifications hop, then fade away.
With every close, a message clear,
"Success or fail," we hold so dear.
So let us cheer for changes bright,
In code we trust, our future's light! 🌟


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. (Experiment)
  • @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 Dec 5, 2024
Copy link

netlify bot commented Dec 5, 2024

Deploy Preview for reearth-cms ready!

Name Link
🔨 Latest commit 1a27b36
🔍 Latest deploy log https://app.netlify.com/sites/reearth-cms/deploys/675be5a3b5b2ce0008d155fd
😎 Deploy Preview https://deploy-preview-1338--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

@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

🧹 Outside diff range and nitpick comments (6)
web/e2e/common/notification.ts (1)

6-13: LGTM! More robust notification validation.

The switch from text-based to attribute-based validation is a good improvement that:

  • Eliminates i18n-related flakiness
  • Provides more precise validation
  • Adds verification that the alert is properly hidden

Consider adding a timeout parameter to handle slow UI updates:

-export async function closeNotification(page: Page, isSuccess = true) {
+export async function closeNotification(page: Page, isSuccess = true, timeout = 5000) {
   const text = isSuccess ? "check-circle" : "close-circle";
-  await expect(page.getByRole("alert").last().getByRole("img")).toHaveAttribute("aria-label", text);
+  await expect(page.getByRole("alert").last().getByRole("img")).toHaveAttribute("aria-label", text, { timeout });
   await page
     .locator(".ant-notification-notice")
     .last()
     .locator(".ant-notification-notice-close")
     .click();
-  await expect(page.getByRole("alert").last()).toBeHidden();
+  await expect(page.getByRole("alert").last()).toBeHidden({ timeout });
 }
web/e2e/project/item/metadata/update.spec.ts (1)

33-36: Enhance test coverage for metadata updates.

While the added validation is good, consider strengthening the test by:

  1. Verifying the persisted state after page reload
  2. Adding negative test cases (invalid inputs)
  3. Testing concurrent updates

Example enhancement:

test("Updating metadata added later from table has succeeded", async ({ page }) => {
  // ... existing code ...
  
  // Verify persisted state
  await page.reload();
  await expect(page.getByLabel("boolean")).toHaveAttribute("aria-checked", "true");
  
  // Test concurrent updates
  const promises = [
    page.getByRole("switch").click(),
    page.getByRole("switch").click(),
  ];
  await Promise.all(promises);
  await expect(page.getByLabel("boolean")).toHaveAttribute("aria-checked", "false");
});
web/src/components/molecules/Content/RenderField/index.tsx (1)

Line range hint 13-17: Add validation to prevent redundant updates.

The update callback is passed directly without any validation, which could lead to unnecessary backend queries.

Consider memoizing the update callback and adding validation:

+import { useCallback, useMemo } from "react";
+
 export const renderField = (
   el: { props: { children: string | string[] } },
   field: Field,
   update?: (value?: string | string[] | boolean, index?: number) => void,
 ) => {
   const value = el.props.children;
   const items = Array.isArray(value) ? value : [value];
+
+  const handleUpdate = useCallback((newValue?: string | string[] | boolean, index?: number) => {
+    if (JSON.stringify(newValue) === JSON.stringify(value)) return;
+    update?.(newValue, index);
+  }, [value, update]);
web/src/components/molecules/Content/RenderField/ItemFormat.tsx (2)

34-44: Consider adding validation in handleTextBlur

While the implementation correctly prevents unnecessary updates, consider adding validation for the text input, similar to the URL validation.

 const handleTextBlur = useCallback(
   (e: FocusEvent<HTMLInputElement>) => {
     const value = e.target.value;
     if (itemState === value) {
       return;
     }
+    if (field.typeProperty?.maxLength && value.length > field.typeProperty.maxLength) {
+      Notification.error({ 
+        message: t("Maximum length error: Character count in {{field}} field exceeds the maximum number!", 
+        { field: field.name }) 
+      });
+      return;
+    }
     update?.(value, index);
     setItemState(value);
   },
-  [index, itemState, update],
+  [index, itemState, update, field, t],
 );

47-61: Add URL sanitization before validation

The URL validation is good, but consider sanitizing the URL input to handle common issues like leading/trailing spaces.

 const handleUrlBlur = useCallback(
   (e: FocusEvent<HTMLInputElement>) => {
-    const value = e.target.value;
+    const value = e.target.value.trim();
     if (itemState === value) {
       setIsEditable(false);
       return;
     }
     if (value && !validateURL(value)) {
       Notification.error({ message: t("Please input a valid URL") });
       return;
     }
     update?.(value, index);
     setItemState(value);
     setIsEditable(false);
   },
   [index, itemState, t, update],
 );
web/src/components/organisms/Project/Content/ContentList/hooks.ts (1)

243-274: Consider extracting validation logic into a separate function.

The validation implementation is correct and thorough, handling both required fields and maximum length constraints. However, the validation logic could be extracted into a separate function for better maintainability and reusability.

Consider refactoring the validation logic like this:

+const validateMetadataFields = (
+  fields: ItemField[],
+  metaFieldsMap: Map<string, any>
+): { requiredErrorFields: string[], maxLengthErrorFields: string[] } => {
+  const requiredErrorFields: string[] = [];
+  const maxLengthErrorFields: string[] = [];
+
+  fields.forEach(field => {
+    const metaField = metaFieldsMap.get(field.schemaFieldId);
+    const fieldValue = field.value;
+
+    if (metaField?.required) {
+      if (Array.isArray(fieldValue)) {
+        if (fieldValue.every(v => checkIfEmpty(v))) {
+          requiredErrorFields.push(metaField.key);
+        }
+      } else if (checkIfEmpty(fieldValue)) {
+        requiredErrorFields.push(metaField.key);
+      }
+    }
+
+    const maxLength = metaField?.typeProperty?.maxLength;
+    if (maxLength) {
+      if (Array.isArray(fieldValue)) {
+        if (fieldValue.some(v => typeof v === "string" && v.length > maxLength)) {
+          maxLengthErrorFields.push(metaField.key);
+        }
+      } else if (typeof fieldValue === "string" && fieldValue.length > maxLength) {
+        maxLengthErrorFields.push(metaField.key);
+      }
+    }
+  });
+
+  return { requiredErrorFields, maxLengthErrorFields };
+};

Then use it in the handleMetaItemUpdate function:

 if (metadata?.fields && metadata.id) {
-  const requiredErrorFields: string[] = [];
-  const maxLengthErrorFields: string[] = [];
+  const { requiredErrorFields, maxLengthErrorFields } = validateMetadataFields(
+    metadata.fields,
+    metaFieldsMap
+  );
   const fields = metadata.fields.map(field => {
     // ... existing field mapping logic
   });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7fd205a and 42d392e.

📒 Files selected for processing (8)
  • web/e2e/common/notification.ts (1 hunks)
  • web/e2e/project/item/metadata/tag.spec.ts (1 hunks)
  • web/e2e/project/item/metadata/update.spec.ts (1 hunks)
  • web/src/components/molecules/Content/RenderField/ItemFormat.tsx (3 hunks)
  • web/src/components/molecules/Content/RenderField/index.tsx (1 hunks)
  • web/src/components/organisms/Project/Content/ContentList/hooks.ts (2 hunks)
  • web/src/i18n/translations/en.yml (2 hunks)
  • web/src/i18n/translations/ja.yml (2 hunks)
🔇 Additional comments (9)
web/src/components/molecules/Content/RenderField/index.tsx (1)

49-52: ⚠️ Potential issue

Verify impact of removing field.multiple check.

The simplified condition could allow updates on multiple fields that weren't previously allowed. This might lead to unexpected behavior.

Let's check for any existing multiple field usage:

Consider adding validation before the update:

   if ((field.type === "Text" || field.type === "Date" || field.type === "URL") && update) {
+    if (field.multiple) {
+      console.warn(`Updating multiple ${field.type} fields directly is not supported`);
+      return <span>-</span>;
+    }
     return <ItemFormat item="" field={field} update={update} />;
   }
web/src/components/molecules/Content/RenderField/ItemFormat.tsx (2)

11-11: LGTM: New imports enhance error handling and internationalization

The additions of Notification and useT support proper error handling with localized messages.

Also applies to: 17-17


138-138: LGTM: Tooltip prop update

The change from showArrow to arrow={false} aligns with the component's API.

web/e2e/project/item/metadata/tag.spec.ts (1)

76-76: LGTM: Enhanced test reliability with notification handling

The addition of closeNotification calls after actions ensures a clean state for subsequent test steps.

Also applies to: 77-77

✅ Verification successful

Based on the extensive search results, I can see that closeNotification is consistently used across many E2E test files after actions that trigger notifications, such as saving, creating, updating, or deleting items. The pattern shows that closeNotification is called after operations that modify state to ensure a clean test environment.

LGTM: Proper notification handling pattern verified

The codebase shows consistent usage of closeNotification after state-modifying operations across E2E tests, confirming this is an established pattern for maintaining test reliability. The addition of this call in the reviewed file follows the same best practice.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify consistent notification handling across all E2E tests
rg -l 'closeNotification' 'web/e2e/**/*.ts' | while read -r file; do
  echo "=== $file ==="
  rg -A 2 'click\(' "$file" | rg -v 'closeNotification' || true
done

Length of output: 114


Script:

#!/bin/bash
# Let's first verify if the file exists and check its content
fd -e ts -p 'web/e2e'

# Then search for closeNotification usage
rg 'closeNotification' -l

# Finally, let's look for click patterns followed by notification handling
rg -A 2 'click\(' 'web/e2e'

Length of output: 110251

web/src/i18n/translations/en.yml (2)

488-489: LGTM: Clear and well-structured error messages

The new error messages use proper interpolation syntax with {{field}} and provide clear feedback to users.


521-521: LGTM: Consistent success message

The updated success message maintains consistency with other success messages in the application.

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

5-5: LGTM! Good reuse of existing validation utility.

The addition of checkIfEmpty utility function promotes code reuse and consistency in validation logic across the application.


232-235: LGTM! Improved field value handling.

The changes properly handle different field types:

  • Tag type fields are handled using selectedTagIdsGet
  • Empty values are handled consistently
  • Array and single value fields are handled appropriately
web/src/i18n/translations/ja.yml (1)

488-489: LGTM! Clear and consistent error messages.

The new translations:

  • Provide clear feedback for validation errors
  • Use proper placeholders for field names
  • Maintain consistency with existing message patterns

Also applies to: 521-521

@caichi-t caichi-t enabled auto-merge (squash) December 13, 2024 07:44
Copy link

@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

🧹 Outside diff range and nitpick comments (2)
web/src/components/organisms/Project/Content/ContentList/hooks.ts (2)

228-243: Field value normalization logic needs refactoring

The field value normalization logic is complex and handles multiple cases. Consider extracting this into a separate function for better maintainability.

+ const normalizeFieldValue = (field: ItemField, value?: string | string[] | boolean | boolean[], index?: number) => {
+   if (Array.isArray(field.value)) {
+     if (field.type === "Tag") {
+       const tags = metaField?.typeProperty?.tags;
+       return tags ? selectedTagIdsGet(value as string[], tags) : [];
+     }
+     field.value[index ?? 0] = value === "" ? undefined : value;
+     return field.value;
+   }
+   return value ?? "";
+ };

266-274: Consider grouping error notifications

Multiple individual notifications might overwhelm the user. Consider grouping them into a single notification with a list of errors.

- requiredErrorFields.forEach(field => {
-   Notification.error({ message: t("Required field error", { field }) });
- });
- maxLengthErrorFields.forEach(field => {
-   Notification.error({ message: t("Maximum length error", { field }) });
- });
+ if (requiredErrorFields.length || maxLengthErrorFields.length) {
+   const errors = [
+     ...requiredErrorFields.map(field => t("Required field error", { field })),
+     ...maxLengthErrorFields.map(field => t("Maximum length error", { field }))
+   ];
+   Notification.error({
+     message: t("Validation Errors"),
+     description: errors.join('\n')
+   });
+ }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 42d392e and 1a27b36.

📒 Files selected for processing (1)
  • web/src/components/organisms/Project/Content/ContentList/hooks.ts (2 hunks)
🔇 Additional comments (2)
web/src/components/organisms/Project/Content/ContentList/hooks.ts (2)

5-5: LGTM: Import statement is correctly placed

The import of checkIfEmpty utility aligns with the PR's objective of implementing value validation.


232-235: Verify tag handling edge cases

The tag handling logic needs verification for edge cases, such as empty arrays or invalid tag IDs.

✅ Verification successful

Let me check the implementation of selectedTagIdsGet to verify the edge case handling.


Tag handling is properly implemented for edge cases

The implementation of selectedTagIdsGet in web/src/components/molecules/Content/utils.ts already handles edge cases appropriately:

  • Empty arrays: When value.length is 0, it returns an empty array []
  • Invalid tag IDs: The filter operation ensures only valid tags (those present in the tags array) are included
  • Null/undefined tags: The code checks for tags existence before calling selectedTagIdsGet
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Tag field usage and potential edge cases
ast-grep --pattern 'type === "Tag"' 
rg -A 5 'selectedTagIdsGet'

Length of output: 2670


Script:

#!/bin/bash
# Get the full implementation of selectedTagIdsGet and surrounding code
ast-grep --pattern 'export const selectedTagIdsGet = $_'

# Check for Tag type definition
rg -A 5 'type Tag'

Length of output: 3862

Comment on lines +244 to +262
if (metaField?.required) {
if (Array.isArray(fieldValue)) {
if (fieldValue.every(v => checkIfEmpty(v))) {
requiredErrorFields.push(metaField.key);
}
} else if (checkIfEmpty(fieldValue)) {
requiredErrorFields.push(metaField.key);
}
}
const maxLength = metaField?.typeProperty?.maxLength;
if (maxLength) {
if (Array.isArray(fieldValue)) {
if (fieldValue.some(v => typeof v === "string" && v.length > maxLength)) {
maxLengthErrorFields.push(metaField.key);
}
} else if (typeof fieldValue === "string" && fieldValue.length > maxLength) {
maxLengthErrorFields.push(metaField.key);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validation logic could be simplified

The validation checks are nested deeply and handle multiple cases. Consider extracting these into separate validation functions for better readability and maintainability.

+ const validateRequiredField = (fieldValue: any, metaField?: MetadataField) => {
+   if (!metaField?.required) return true;
+   if (Array.isArray(fieldValue)) {
+     return !fieldValue.every(v => checkIfEmpty(v));
+   }
+   return !checkIfEmpty(fieldValue);
+ };

+ const validateMaxLength = (fieldValue: any, maxLength?: number) => {
+   if (!maxLength) return true;
+   if (Array.isArray(fieldValue)) {
+     return !fieldValue.some(v => typeof v === "string" && v.length > maxLength);
+   }
+   return !(typeof fieldValue === "string" && fieldValue.length > maxLength);
+ };

Committable suggestion skipped: line range outside the PR's diff.

@caichi-t caichi-t merged commit 646ae71 into main Dec 13, 2024
22 checks passed
@caichi-t caichi-t deleted the fix-web/update-metadata-from-table branch December 13, 2024 07:47
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.

2 participants