Skip to content

[OSDEV-1116] Implement contribution record page#397

Merged
Innavin369 merged 112 commits intomainfrom
OSDEV-1116-implement-contribution-record-page
Nov 12, 2024
Merged

[OSDEV-1116] Implement contribution record page#397
Innavin369 merged 112 commits intomainfrom
OSDEV-1116-implement-contribution-record-page

Conversation

@Innavin369
Copy link
Contributor

@Innavin369 Innavin369 commented Nov 1, 2024

OSDEV-1116 - A new Contribution Record Page has been developed to enable quick identification and moderation of contributions. This page includes two main sections: Moderation Event Data and Potential Matches, along with a set of buttons designed to facilitate the moderation process.

Screenshot 2024-11-05 at 14 31 24

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 (2)
src/react/src/util/propTypes.js (2)

515-532: LGTM! Consider adding JSDoc comments.

The PropType definition is well-structured with proper validation. Consider adding JSDoc comments to document the purpose and structure of the moderation event data.

Example documentation:

+/**
+ * PropType for moderation events that validates the structure of moderation data
+ * @property {number} moderation_id - Unique identifier for the moderation event
+ * @property {string} created_at - Creation timestamp
+ * @property {string} updated_at - Last update timestamp
+ * @property {string} [os_id] - Optional OpenSupply ID
+ * ...
+ */
 export const moderationEventPropType = shape({

534-564: LGTM! Consider enhancing type safety for optional fields.

The PropType definition is well-structured. Consider explicitly documenting optional fields and adding validation for array field items.

Apply these improvements:

 export const potentialMatchesPropType = arrayOf(
     shape({
         os_id: string.isRequired,
         name: string.isRequired,
         address: string.isRequired,
-        sector: arrayOf(string),
-        parent_company: string,
-        product_type: arrayOf(string),
-        location_type: arrayOf(string),
-        processing_type: arrayOf(string),
+        sector: arrayOf(string.isRequired), // Optional array
+        parent_company: string, // Optional
+        product_type: arrayOf(string.isRequired), // Optional array
+        location_type: arrayOf(string.isRequired), // Optional array
+        processing_type: arrayOf(string.isRequired), // Optional array
         number_of_workers: shape({
             min: number.isRequired,
             max: number.isRequired,
         }),
         coordinates: shape({
             lat: number.isRequired,
             lng: number.isRequired,
         }),
-        local_name: string,
-        description: string,
-        business_url: string,
-        minimum_order_quantity: string,
-        average_lead_time: string,
-        percent_female_workers: number,
-        affiliations: arrayOf(string),
-        certifications_standards_regulations: arrayOf(string),
-        historical_os_id: arrayOf(string),
+        local_name: string, // Optional
+        description: string, // Optional
+        business_url: string, // Optional
+        minimum_order_quantity: string, // Optional
+        average_lead_time: string, // Optional
+        percent_female_workers: number, // Optional
+        affiliations: arrayOf(string.isRequired), // Optional array
+        certifications_standards_regulations: arrayOf(string.isRequired), // Optional array
+        historical_os_id: arrayOf(string.isRequired), // Optional array
         country: productionLocationCountryPropType.isRequired,
-        claim_status: string,
+        claim_status: string, // Optional
     }),
 );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ef71a57 and bc3f471.

📒 Files selected for processing (1)
  • src/react/src/util/propTypes.js (2 hunks)
🔇 Additional comments (1)
src/react/src/util/propTypes.js (1)

463-463: LGTM! Fixed typo in isRequired.

The typo in isRequired has been correctly fixed.

roman-stolar
roman-stolar previously approved these changes Nov 11, 2024
Copy link
Contributor

@roman-stolar roman-stolar left a comment

Choose a reason for hiding this comment

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

LGTM, please check CodeRabbit comments and resolved if it unnecessary.

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)
src/react/src/components/Dashboard/DashboardContributionRecord.jsx (2)

24-34: Consider moving styles to the makeDashboardContributionRecordStyles utility

The styles object could be moved to the makeDashboardContributionRecordStyles utility for better maintainability and consistency with other styles.


102-162: Optimize potential matches rendering performance

Consider these optimizations for the potential matches list:

  1. Extract the list item into a separate component to prevent unnecessary re-renders
  2. Use useMemo for the filtered list

Example implementation:

+const PotentialMatchListItem = React.memo(({ 
+    osId, 
+    name, 
+    address, 
+    claimStatus, 
+    index, 
+    totalLength,
+    onConfirm,
+    classes 
+}) => (
+    <React.Fragment key={osId}>
+        <ListItem className={classes.listItemStyle}>
+            {/* ... existing ListItem content ... */}
+        </ListItem>
+        {index < totalLength - 1 && (
+            <Divider className={classes.innerDividerStyle} component="li" />
+        )}
+    </React.Fragment>
+));

// In the main component:
const memoizedPotentialMatches = useMemo(() => 
    potentialMatches.map((match, index) => (
        <PotentialMatchListItem
            key={match.osId}
            {...match}
            index={index}
            totalLength={potentialMatches.length}
            classes={classes}
            onConfirm={() => {}} // TODO: Implement
        />
    )),
    [potentialMatches, classes]
);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between bc3f471 and 5ec2510.

📒 Files selected for processing (1)
  • src/react/src/components/Dashboard/DashboardContributionRecord.jsx (1 hunks)
🧰 Additional context used
📓 Learnings (1)
src/react/src/components/Dashboard/DashboardContributionRecord.jsx (1)
Learnt from: Innavin369
PR: opensupplyhub/open-supply-hub#397
File: src/react/src/components/Dashboard/DashboardContributionRecord.jsx:25-35
Timestamp: 2024-11-08T14:17:38.915Z
Learning: In `src/react/src/components/Dashboard/DashboardContributionRecord.jsx`, using inline styles with the `List` component is acceptable and functions correctly in this context.
🔇 Additional comments (2)
src/react/src/components/Dashboard/DashboardContributionRecord.jsx (2)

46-49: LGTM! Proper useEffect implementation

The useEffect hook is correctly implemented with proper dependency tracking for data fetching.


201-217: LGTM! Well-defined props configuration

Props are well-defined with appropriate default values and type checking.

@roman-stolar
Copy link
Contributor

Quality Gate Failed Quality Gate failed

Failed conditions 8.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarCloud

@Innavin369 Please add // NOSONAR: justification for ignoring specific code to the lines of duplicated code to be passed by SonarQube.

@mazursasha1990
Copy link
Contributor

@Innavin369 If you intend to add a comment for ignoring specific code, please do so on the mocked data that will be removed after the integration task is implemented. Otherwise, you can modify the data in one of the files (tests or mocks) to prevent repetition.

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 (1)
src/react/src/actions/dashboardContributionRecord.js (1)

52-52: Add Jira ticket reference to TODO comment.

For consistency with other TODOs in the file (like the one referencing OSDEV-1347), please add the corresponding Jira ticket number for tracking the API integration task.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5ec2510 and bd5e551.

📒 Files selected for processing (1)
  • src/react/src/actions/dashboardContributionRecord.js (1 hunks)
🔇 Additional comments (3)
src/react/src/actions/dashboardContributionRecord.js (3)

1-24: LGTM! Well-structured action creators.

The action creators are well-organized, following consistent naming patterns and covering all necessary states (start, complete, fail, cleanup).


91-132: LGTM! Well-implemented async action creators.

The fetch functions follow Redux best practices with proper:

  • Async/await usage
  • Error handling with logErrorAndDispatchFailure
  • Action dispatching sequence (start -> complete/fail)

27-88: Verify data sanitization for sensitive fields.

The mock data contains potentially sensitive business information. Ensure that the production API endpoints properly sanitize sensitive fields before sending responses.

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 (3)
src/react/src/actions/dashboardContributionRecord.js (3)

26-86: Consider enhancing mock data quality and security.

While the mock data structure is comprehensive, consider:

  1. Using more realistic non-zero values for fields like number_of_workers, coordinates, and percent_female_workers
  2. Adding JSDoc comments to document the expected data types and ranges
  3. Marking the mock data as // NOSONAR as suggested in PR comments to handle duplication concerns

88-107: Consider reducing duplication in fetch functions.

Both fetch functions share similar Promise-based mock implementation patterns. Consider extracting the common logic into a reusable utility function.

Example refactor:

+const createMockFetch = (mockData, delay = 1000) => 
+  new Promise(resolve => setTimeout(() => resolve({ data: mockData }), delay));

 export function fetchModerationEvent(moderationID) {
     return async dispatch => {
         dispatch(startFetchingModerationEvent());
-        return new Promise(resolve => {
-            setTimeout(() => resolve({ data: eventMockData }), 1000);
-        })
+        return createMockFetch(eventMockData)
             .then(({ data }) => dispatch(completeFetchingModerationEvent(data)))
             .catch(err =>
                 dispatch(
                     logErrorAndDispatchFailure(
                         err,
                         'An error prevented fetching moderation event',
                         failFetchingModerationEvent,
                     ),
                 ),
             );
     };
 }

Also applies to: 109-130


1-130: Consider splitting into domain-specific modules.

The file currently handles two distinct domains (moderation events and potential matches) that will eventually connect to different API endpoints. Consider splitting these into separate modules:

  • moderationEventActions.js
  • potentialMatchesActions.js

This would improve maintainability and make the transition to real API calls cleaner.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between bd5e551 and 464f282.

📒 Files selected for processing (2)
  • src/react/src/__tests__/components/DashboardContributionRecord.test.js (1 hunks)
  • src/react/src/actions/dashboardContributionRecord.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/react/src/tests/components/DashboardContributionRecord.test.js
🔇 Additional comments (1)
src/react/src/actions/dashboardContributionRecord.js (1)

1-24: LGTM! Well-structured action creators.

The action creators follow Redux best practices with clear naming conventions and proper separation of concerns.

Copy link
Contributor

@mazursasha1990 mazursasha1990 left a comment

Choose a reason for hiding this comment

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

Please fix the warnings in the browser console:
Screenshot from 2024-11-11 19-32-59
Screenshot from 2024-11-11 19-32-37

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: 2

🧹 Outside diff range and nitpick comments (6)
src/react/src/reducers/DashboardContributionRecordReducer.js (3)

14-25: Simplify Object.freeze() usage.

Multiple nested Object.freeze() calls are redundant as the top-level freeze is sufficient for immutability.

 const initialState = Object.freeze({
-    singleModerationEvent: Object.freeze({
+    singleModerationEvent: {
         fetching: false,
         error: null,
-        event: Object.freeze({}),
+        event: {},
-    }),
+    },
-    potentialMatches: Object.freeze({
+    potentialMatches: {
-        potentialMatches: Object.freeze([]),
+        potentialMatches: [],
         fetching: false,
         error: null,
-    }),
+    },
 });

20-22: Rename nested 'potentialMatches' property to avoid confusion.

Having the same name for both the parent and child property can lead to confusion. Consider renaming the nested property to 'matches' or 'items'.

     potentialMatches: {
-        potentialMatches: [],
+        matches: [],
         fetching: false,

29-54: Reduce repetition in accessing initial state values.

The handlers frequently access initial state values. Consider destructuring these values at the top of the reducer for better maintainability.

 export default createReducer(
     {
+        // Destructure initial values for reuse
+        const {
+            singleModerationEvent: { fetching: initialFetching, error: initialError }
+        } = initialState;
+
         [startFetchingSingleModerationEvent]: state =>
             update(state, {
                 singleModerationEvent: {
                     fetching: { $set: true },
-                    error: { $set: initialState.singleModerationEvent.error },
+                    error: { $set: initialError },
                 },
             }),
         [failFetchingSingleModerationEvent]: (state, error) =>
             update(state, {
                 singleModerationEvent: {
                     fetching: {
-                        $set: initialState.singleModerationEvent.fetching,
+                        $set: initialFetching,
                     },
                     error: { $set: error },
                 },
             }),
src/react/src/actions/dashboardContributionRecord.js (1)

26-86: Consider adding data validation for critical fields.

While this is mock data that will be replaced (OSDEV-1347), consider adding validation for business-critical fields to better simulate API behavior:

  • coordinates: (0,0) is an invalid location (middle of ocean)
  • number_of_workers: Needs min/max range validation
  • percent_female_workers: Should be between 0-100
  • business_url: Should validate URL format

Consider adding validation helpers:

const isValidCoordinates = ({ lat, lng }) => (
  lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180
);

const isValidPercentage = (value) => (
  Number.isInteger(value) && value >= 0 && value <= 100
);

const isValidWorkerCount = ({ min, max }) => (
  Number.isInteger(min) && Number.isInteger(max) && min >= 0 && min <= max
);
src/react/src/components/Dashboard/DashboardContributionRecord.jsx (2)

58-63: Add aria-label to loading spinner

The CircularProgress component should have an aria-label for better accessibility.

 <CircularProgress
     size={25}
     className={classes.loaderStyles}
+    aria-label="Loading moderation event data"
 />

93-98: Add default values to destructured properties

Add default values when destructuring match properties to prevent undefined errors if any property is missing.

 {
-    osId,
-    name,
-    address,
-    claim_status: claimStatus,
+    osId = '',
+    name = 'Unknown',
+    address = 'No address provided',
+    claim_status: claimStatus = 'Unknown',
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 464f282 and 60f4429.

📒 Files selected for processing (7)
  • src/react/src/__tests__/components/DashboardContributionRecord.test.js (1 hunks)
  • src/react/src/__tests__/components/DashboardModerationQueueListTable.test.js (2 hunks)
  • src/react/src/actions/dashboardContributionRecord.js (1 hunks)
  • src/react/src/components/Dashboard.jsx (5 hunks)
  • src/react/src/components/Dashboard/DashboardContributionRecord.jsx (1 hunks)
  • src/react/src/reducers/DashboardContributionRecordReducer.js (1 hunks)
  • src/react/src/util/styles.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/react/src/tests/components/DashboardContributionRecord.test.js
  • src/react/src/tests/components/DashboardModerationQueueListTable.test.js
  • src/react/src/components/Dashboard.jsx
  • src/react/src/util/styles.js
🔇 Additional comments (5)
src/react/src/reducers/DashboardContributionRecordReducer.js (2)

1-12: LGTM! Dependencies and imports are well organized.

The choice of redux-act and immutability-helper is appropriate for managing immutable state updates in Redux.


77-77: LGTM! Cleanup handler properly implemented.

The cleanup handler correctly resets the state to prevent memory leaks, as requested in previous reviews.

src/react/src/actions/dashboardContributionRecord.js (2)

1-24: LGTM! Well-structured Redux action creators.

The action creators follow Redux best practices with clear naming patterns and proper use of redux-act.


88-132: LGTM! Well-implemented Redux async actions.

The async action implementations follow Redux best practices:

  • Proper dispatch of start/complete/fail actions
  • Good error handling using logErrorAndDispatchFailure
  • Clear separation of concerns between moderation events and potential matches
src/react/src/components/Dashboard/DashboardContributionRecord.jsx (1)

34-37: Consider sequential API calls to prevent race conditions

The current implementation makes parallel API calls for fetchEvent and fetchMatches. Consider making these calls sequential if the second call depends on data from the first call, or if order matters for proper UI updates.

Consider this approach:

-useEffect(() => {
-    fetchEvent();
-    fetchMatches();
-}, [fetchEvent, fetchMatches]);
+useEffect(() => {
+    const fetchData = async () => {
+        await fetchEvent();
+        await fetchMatches();
+    };
+    fetchData();
+}, [fetchEvent, fetchMatches]);

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: 3

🧹 Outside diff range and nitpick comments (4)
src/react/src/actions/dashboardContributionRecord.js (1)

94-96: Consider using async/await for better readability.

The Promise chain could be simplified using async/await syntax:

-        return new Promise(resolve => {
-            setTimeout(() => resolve({ data: eventMockData }), 1000);
-        })
+        try {
+            const data = await new Promise(resolve =>
+                setTimeout(() => resolve({ data: eventMockData }), 1000)
+            );
+            return dispatch(completeFetchingSingleModerationEvent(data.data));
+        } catch (err) {
+            return dispatch(
+                logErrorAndDispatchFailure(
+                    err,
+                    'An error prevented fetching moderation event',
+                    failFetchingSingleModerationEvent,
+                )
+            );
+        }
src/react/src/components/Dashboard/DashboardContributionRecord.jsx (3)

34-37: Consider handling potential race conditions in data fetching

The concurrent execution of fetchEvent and fetchMatches could lead to race conditions. Consider implementing a sequential fetch pattern or ensuring proper error handling for both requests.

Consider this pattern:

 useEffect(() => {
-    fetchEvent();
-    fetchMatches();
+    const fetchData = async () => {
+        try {
+            await fetchEvent();
+            await fetchMatches();
+        } catch (error) {
+            // Handle any errors that occurred during fetching
+        }
+    };
+    fetchData();
 }, [fetchEvent, fetchMatches]);

91-149: Optimize list rendering performance

The list rendering could be optimized by:

  1. Memoizing the list items to prevent unnecessary re-renders
  2. Using virtualization for large lists

Consider implementing:

+const MemoizedListItem = React.memo(({ osId, name, address, claimStatus, index, totalLength, onConfirm }) => (
+    <React.Fragment key={osId}>
+        <ListItem className={classes.listItemStyle}>
+            {/* ... existing list item content ... */}
+        </ListItem>
+        {index < totalLength - 1 && (
+            <Divider className={classes.innerDividerStyle} component="li" />
+        )}
+    </React.Fragment>
+));

 <List>
-    {matches.map((
-        {
-            os_id: osId,
-            name,
-            address,
-            claim_status: claimStatus,
-        },
-        index,
-    ) => (
-        // ... existing list item JSX ...
-    ))}
+    {matches.map((match, index) => (
+        <MemoizedListItem
+            key={match.os_id}
+            {...match}
+            index={index}
+            totalLength={matches.length}
+            onConfirm={() => {/* implement confirm handler */}}
+        />
+    ))}
 </List>

206-226: Optimize Redux state mapping

The current state mapping could lead to unnecessary re-renders. Consider implementing selector memoization using reselect.

Example implementation:

import { createSelector } from 'reselect';

const selectDashboardContributionRecord = state => state.dashboardContributionRecord;

const selectEvent = createSelector(
  [selectDashboardContributionRecord],
  record => record.singleModerationEvent.event
);

const selectMatches = createSelector(
  [selectDashboardContributionRecord],
  record => record.potentialMatches.matches
);

const mapStateToProps = state => ({
  event: selectEvent(state),
  matches: selectMatches(state),
  // ... other selectors
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 60f4429 and ad54324.

📒 Files selected for processing (5)
  • src/react/src/__tests__/components/DashboardContributionRecord.test.js (1 hunks)
  • src/react/src/actions/dashboardContributionRecord.js (1 hunks)
  • src/react/src/components/Dashboard/DashboardContributionRecord.jsx (1 hunks)
  • src/react/src/reducers/DashboardContributionRecordReducer.js (1 hunks)
  • src/react/src/util/propTypes.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/react/src/tests/components/DashboardContributionRecord.test.js
  • src/react/src/reducers/DashboardContributionRecordReducer.js
🔇 Additional comments (6)
src/react/src/actions/dashboardContributionRecord.js (4)

1-24: LGTM! Well-structured action creators.

The action creators follow Redux best practices with consistent naming patterns and include proper cleanup actions for state management.


112-133: Apply the same async/await pattern for consistency.

For consistency with the previous function, consider applying the same async/await pattern here as well.


26-49: Consider data validation and PII handling.

While this is mock data, the actual implementation should include:

  1. Date validation for created_at and updated_at
  2. Proper handling of PII fields like contributor_name
  3. Validation for moderation_status enum values

Let's check if there are any existing data validation patterns:


50-87: ⚠️ Potential issue

Add validation for numerical fields and standardize empty values.

The potential matches data structure needs validation for:

  1. Coordinates (lat: 91.7896718 is invalid as it exceeds 90°)
  2. number_of_workers range validation
  3. percent_female_workers (0-100 range)

Also, consider using null instead of empty strings for business_url, minimum_order_quantity, and average_lead_time.

Let's check for existing validation patterns:

src/react/src/util/propTypes.js (2)

463-463: LGTM: Fixed the required validation for facility_name.

The change correctly marks the facility_name field as required, which aligns with the schema requirements.


534-564: LGTM: Well-structured type definition for potential matches.

The potentialMatchesPropType is well-defined with:

  • Proper validation for required fields
  • Correct handling of optional fields
  • Strong typing for nested objects

@sonarqubecloud
Copy link

Copy link
Contributor

@mazursasha1990 mazursasha1990 left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@roman-stolar roman-stolar left a comment

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants