Skip to content

Conversation

hiennguyen92
Copy link
Contributor

@hiennguyen92 hiennguyen92 commented Sep 19, 2025

New Pull Request Checklist

Issue Description

Fixes #2626

Closes: #2626

Approach

  • Created a new component PushAudienceFilter to handle audience filtering logic.
  • Integrated PushAudienceFilter into PushAudienceDialog.

TODOs before merging

  • Add tests — N/A
  • Add changes to documentation — N/A

Summary by CodeRabbit

  • New Features

    • Cloud Code now uses a full editor with inline saving, status feedback, and error messaging.
    • Save button supports a waiting state for clearer save progress.
    • Improved Push Audience filtering with a dedicated UI for selecting fields, constraints, and values.
  • Bug Fixes

    • Handles cases with no available audience conditions and clears messages when rows are removed.
    • Cloud Code view updates correctly on path changes and redirects to the first available file if the requested file is missing.

Update readme for store dashboard settings parse-community#2555

Signed-off-by: Hien Nguyen <hiennguyen92@gmail.com>
chore(docs): update README with dashboard config options

Signed-off-by: Hien Nguyen <hiennguyen92@gmail.com>
Signed-off-by: Hien Nguyen <hiennguyen92@gmail.com>
Signed-off-by: Hien Nguyen <hiennguyen92@gmail.com>
Signed-off-by: Hien Nguyen <hiennguyen92@gmail.com>
Copy link

I will reformat the title to use the proper commit message syntax.

@parse-github-assistant parse-github-assistant bot changed the title fix(push): resolve error when creating audience by adding PushAudienceFilter component (#2626) fix(push): Resolve error when creating audience by adding PushAudienceFilter component (#2626) Sep 19, 2025
Copy link

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Sep 19, 2025

🎉 Snyk checks have passed. No issues have been found so far.

security/snyk check is complete. No issues have been found. (View Details)

Copy link

uffizzi-cloud bot commented Sep 19, 2025

Uffizzi Ephemeral Environment deployment-65347

⌚ Updated Sep 19, 2025, 18:44 UTC

☁️ https://app.uffizzi.com/github.com/parse-community/parse-dashboard/pull/2983

📄 View Application Logs etc.

What is Uffizzi? Learn more

Copy link

coderabbitai bot commented Sep 29, 2025

📝 Walkthrough

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning In addition to the PushAudienceFilter integration, this pull request modifies SaveButton, CloudCode.react.js, and ParseApp.js to introduce cloud code saving and new SaveButton states, which are unrelated to fixing the audience creation error and fall outside the scope of issue #2626. Extract or revert the changes to SaveButton, CloudCode, and ParseApp into separate pull requests so that this PR only contains code relevant to restoring the Create Audience functionality.
Description Check ⚠️ Warning Although the description includes the required checklist, issue reference, approach, and TODOs sections, the Issue Description lacks a brief summary of the actual problem being solved and the TODOs use “N/A” instead of being removed per the template’s instructions. Add a concise description of the issue under “Issue Description” explaining the blank page and TypeError experienced when creating an audience, and remove any TODO items that do not apply rather than marking them “N/A”.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly summarizes the primary change by stating that a PushAudienceFilter component was added to resolve the audience creation error and directly aligns with the pull request’s main purpose without including file lists or vague language.
Linked Issues Check ✅ Passed The changes introduce a new PushAudienceFilter component and integrate it into PushAudienceDialog with appropriate guards for missing filters, which directly address the TypeError and restore the audience creation popup as described in issue #2626.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🧹 Nitpick comments (7)
src/components/PushAudienceDialog/PushAudienceDialog.react.js (1)

103-113: Good empty‑state guard; consider clearing stale errors and double‑check constraints.

  • Nice fix preventing undefined access when no fields are available.
  • Also clear any prior error on successful add to avoid stale banner.
  • Optional: ensure available[field]?.[0] exists before push.

Apply:

   const keys = Object.keys(available);
   if (keys.length === 0) {
     this.setState({
       errorMessage: 'No condition available.',
     });
     return;
   }
-  const field = keys[0];
+  const field = keys[0];
+  // Clear any previous error upon successful add
+  this.setState({ errorMessage: undefined });
src/components/PushAudienceFilter/PushAudienceFilter.react.js (6)

9-9: Avoid shadowing global Map; use an alias and Immutable factory.

Importing Map shadows the global and the file uses new Map(...). Prefer an alias and the factory call.

Apply:

-import { List, Map } from 'immutable';
+import { List, Map as IMap } from 'immutable';

Then replace new Map({...}) with IMap({...}) in this file. See further diffs below. Based on static analysis hints.


15-28: Use Immutable Map factory and tighten compare preservation.

  • Switch to IMap({...}) (no new).
  • Current type check uses typeof; acceptable, but keep consistent.

Apply:

-function changeField(schema, filters, index, newField) {
+function changeField(schema, filters, index, newField) {
   const allowedConstraints = Filters.FieldConstraints[schema[newField].type];
   const current = filters.get(index);
   const constraint = current.get('constraint');
   const compare = current.get('compareTo');
   const defaultCompare = Filters.DefaultComparisons[schema[newField].type];
   const useExisting = allowedConstraints.includes(constraint);
-  const newFilter = new Map({
+  const newFilter = IMap({
     field: newField,
     constraint: useExisting ? constraint : Filters.FieldConstraints[schema[newField].type][0],
     compareTo: useExisting && typeof defaultCompare === typeof compare ? compare : defaultCompare,
   });
   return filters.set(index, newFilter);
 }

30-42: Use Immutable Map factory in constraint change.

Apply:

-  const newFilter = new Map({
+  const newFilter = IMap({
     field: field,
     constraint: newConstraint,
     compareTo: prevCompareTo ?? Filters.DefaultComparisons[compareType],
   });

44-47: Remove unused parameter or use it.

type isn’t used. Drop it to reduce noise and update the caller.

Apply:

-function changeCompareTo(schema, filters, index, type, newCompare) {
+function changeCompareTo(schema, filters, index, newCompare) {
   const newValue = newCompare;
   return filters.set(index, filters.get(index).set('compareTo', newValue));
}

And here:

- onChangeCompareTo: newCompare => {
-   onChange(changeCompareTo(schema, filters, i, compareType, newCompare));
- },
+ onChangeCompareTo: newCompare => {
+   onChange(changeCompareTo(schema, filters, i, newCompare));
+ },

78-108: Sorting: always prefer locale/stringCompare for consistency.

Fallback branch uses default .sort() which is ASCII. To keep consistent ordering with preference branch using stringCompare, use it in both.

Apply:

-else {
-  fields.sort();
-}
+else {
+  fields.sort(stringCompare);
+}

155-163: PropTypes: fix wording and add missing optional props.

  • “comparator” → “constraint”.
  • Declare optional props used: onChange, onSearch, blacklist, className, onDeleteRow.

Apply:

 PushAudienceFilter.propTypes = {
   schema: PropTypes.object.isRequired.describe(
     'A class schema, mapping field names to their Type strings'
   ),
   filters: PropTypes.instanceOf(List).isRequired.describe(
-    'An array of filter objects. Each filter contains "field", "comparator", and "compareTo" fields.'
+    'An array of filter objects. Each filter contains "field", "constraint", and "compareTo" fields.'
   ),
   renderRow: PropTypes.func.isRequired.describe('A function for rendering a row of a filter.'),
+  onChange: PropTypes.func.describe('Called with updated filters when a row changes.'),
+  onSearch: PropTypes.func.describe('Invoked on Enter key. Optional.'),
+  blacklist: PropTypes.arrayOf(PropTypes.string).describe('Constraints to hide. Optional.'),
+  className: PropTypes.string.describe('Class name used to resolve column preferences. Optional.'),
+  onDeleteRow: PropTypes.func.describe('Called before a row is deleted. Optional.'),
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f46c540 and 20d4864.

📒 Files selected for processing (6)
  • src/components/PushAudienceDialog/InstallationCondition.react.js (0 hunks)
  • src/components/PushAudienceDialog/PushAudienceDialog.react.js (3 hunks)
  • src/components/PushAudienceFilter/PushAudienceFilter.react.js (1 hunks)
  • src/components/SaveButton/SaveButton.react.js (1 hunks)
  • src/dashboard/Data/CloudCode/CloudCode.react.js (7 hunks)
  • src/lib/ParseApp.js (1 hunks)
💤 Files with no reviewable changes (1)
  • src/components/PushAudienceDialog/InstallationCondition.react.js
🧰 Additional context used
🧬 Code graph analysis (3)
src/dashboard/Data/CloudCode/CloudCode.react.js (2)
src/lib/generatePath.js (2)
  • params (6-6)
  • generatePath (3-34)
src/components/SaveButton/SaveButton.react.js (1)
  • SaveButton (14-57)
src/components/PushAudienceDialog/PushAudienceDialog.react.js (1)
src/components/PushAudienceFilter/PushAudienceFilter.react.js (1)
  • PushAudienceFilter (53-151)
src/components/PushAudienceFilter/PushAudienceFilter.react.js (2)
src/lib/generatePath.js (2)
  • filters (8-8)
  • filter (12-12)
src/components/Filter/Filter.react.js (1)
  • newConstraint (24-24)
🪛 GitHub Check: Lint
src/dashboard/Data/CloudCode/CloudCode.react.js

[failure] 107-107:
'fileName' is never reassigned. Use 'const' instead


[failure] 102-102:
Trailing spaces not allowed

🪛 Biome (2.1.2)
src/components/PushAudienceFilter/PushAudienceFilter.react.js

[error] 9-9: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Docker linux/amd64
🔇 Additional comments (1)
src/components/PushAudienceDialog/PushAudienceDialog.react.js (1)

13-13: Swap to PushAudienceFilter looks good.

Import path update is correct and aligns with the new component.

Comment on lines +295 to 305
<PushAudienceFilter
schema={this.props.schema}
filters={this.state.filters}
onChange={filters => {
this.setState({ filters }, this.fetchAudienceSize.bind(this));
}}
onDeleteRow={() => {
this.setState({ errorMessage: undefined });
}}
renderRow={props => <InstallationCondition {...props} />}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Pressing Enter in a row may crash: onSearch isn’t provided to child.

PushAudienceFilter calls onSearch() on Enter without optional chaining. Since this dialog doesn’t pass onSearch, users pressing Enter can hit a TypeError. Pass a no‑op or wire it to refresh the audience size.

Apply:

   <PushAudienceFilter
     schema={this.props.schema}
     filters={this.state.filters}
     onChange={filters => {
-      this.setState({ filters }, this.fetchAudienceSize.bind(this));
+      this.setState({ filters, errorMessage: undefined }, this.fetchAudienceSize.bind(this));
     }}
+    onSearch={this.fetchAudienceSize.bind(this)}
     onDeleteRow={() => {
       this.setState({ errorMessage: undefined });
     }}
     renderRow={props => <InstallationCondition {...props} />}
   />
📝 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
<PushAudienceFilter
schema={this.props.schema}
filters={this.state.filters}
onChange={filters => {
this.setState({ filters }, this.fetchAudienceSize.bind(this));
}}
onDeleteRow={() => {
this.setState({ errorMessage: undefined });
}}
renderRow={props => <InstallationCondition {...props} />}
/>
<PushAudienceFilter
schema={this.props.schema}
filters={this.state.filters}
onChange={filters => {
this.setState({ filters, errorMessage: undefined }, this.fetchAudienceSize.bind(this));
}}
onSearch={this.fetchAudienceSize.bind(this)}
onDeleteRow={() => {
this.setState({ errorMessage: undefined });
}}
renderRow={props => <InstallationCondition {...props} />}
/>
🤖 Prompt for AI Agents
In src/components/PushAudienceDialog/PushAudienceDialog.react.js around lines
295 to 305, PushAudienceFilter invokes onSearch() on Enter but this dialog does
not pass an onSearch prop, causing a TypeError when users press Enter; fix by
passing an onSearch prop (either a no-op function or point it to refresh the
audience size, e.g., call this.fetchAudienceSize) so the child can safely call
onSearch without crashing and ensure binding/context is correct when using the
component's method.

Comment on lines +110 to +116
const constraints = Filters.FieldConstraints[schema[field].type].filter(
c => blacklist.indexOf(c) < 0
);
let compareType = schema[field].type;
if (Object.prototype.hasOwnProperty.call(Filters.Constraints[constraint], 'field')) {
compareType = Filters.Constraints[constraint].field;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against missing schema entries to avoid crashes.

If a saved filter references a removed field, schema[field] is undefined and this will throw.

Apply minimal guard:

-const constraints = Filters.FieldConstraints[schema[field].type].filter(
+if (!schema[field]) {
+  // Field no longer exists; skip rendering this row safely.
+  return null;
+}
+const constraints = Filters.FieldConstraints[schema[field].type].filter(
   c => blacklist.indexOf(c) < 0
);

Optionally, surface a UI hint instead of skipping; happy to provide.

📝 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 constraints = Filters.FieldConstraints[schema[field].type].filter(
c => blacklist.indexOf(c) < 0
);
let compareType = schema[field].type;
if (Object.prototype.hasOwnProperty.call(Filters.Constraints[constraint], 'field')) {
compareType = Filters.Constraints[constraint].field;
}
if (!schema[field]) {
// Field no longer exists; skip rendering this row safely.
return null;
}
const constraints = Filters.FieldConstraints[schema[field].type].filter(
c => blacklist.indexOf(c) < 0
);
let compareType = schema[field].type;
if (Object.prototype.hasOwnProperty.call(Filters.Constraints[constraint], 'field')) {
compareType = Filters.Constraints[constraint].field;
}
🤖 Prompt for AI Agents
In src/components/PushAudienceFilter/PushAudienceFilter.react.js around lines
110 to 116, the code assumes schema[field] exists and uses schema[field].type
which will throw if a saved filter references a removed field; add a guard that
checks if schema[field] is defined before accessing .type and bail out (e.g.,
skip this filter, return an empty constraints array, or set a fallback
compareType) and optionally surface a UI hint or console.warn when a field is
missing; ensure both uses of schema[field].type are protected so the component
does not crash.

Comment on lines +138 to +142
onKeyDown: ({ key }) => {
if (key === 'Enter') {
onSearch();
}
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Enter key handler can throw when onSearch is undefined.

Use optional chaining to make it safe.

Apply:

 onKeyDown: ({ key }) => {
   if (key === 'Enter') {
-    onSearch();
+    onSearch?.();
   }
 },
📝 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
onKeyDown: ({ key }) => {
if (key === 'Enter') {
onSearch();
}
},
onKeyDown: ({ key }) => {
if (key === 'Enter') {
onSearch?.();
}
},
🤖 Prompt for AI Agents
In src/components/PushAudienceFilter/PushAudienceFilter.react.js around lines
138 to 142, the onKeyDown handler calls onSearch() directly which can throw if
onSearch is undefined; change the call to use optional chaining so it safely
invokes onSearch only when defined (e.g., call onSearch?.()) and keep the
existing Enter key check unchanged to avoid runtime errors.

Comment on lines +114 to +118
this.setState({ saveState: SaveButton.States.FAILED });
this.setState({ saveError: e.message || e });
}
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Let the save button recover after a failure.

Once saveState is set to FAILED, SaveButton stops wiring onClick, so the user can’t retry the save without reloading the page. Please transition back to WAITING (after surfacing the error) so retries are possible.

You can mirror the success path timing and clear the failure state shortly after logging the error:

     } catch (e) {
-      this.setState({ saveState: SaveButton.States.FAILED });
-      this.setState({ saveError: e.message || e });
+      this.setState({
+        saveState: SaveButton.States.FAILED,
+        saveError: e.message || e,
+      });
+      setTimeout(() => {
+        this.setState({ saveState: SaveButton.States.WAITING });
+      }, 2000);
     }
📝 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
} catch (e) {
this.setState({ saveState: SaveButton.States.FAILED });
this.setState({ saveError: e.message || e });
}
}
} catch (e) {
this.setState({
saveState: SaveButton.States.FAILED,
saveError: e.message || e,
});
setTimeout(() => {
this.setState({ saveState: SaveButton.States.WAITING });
}, 2000);
}
}
🤖 Prompt for AI Agents
In src/dashboard/Data/CloudCode/CloudCode.react.js around lines 114 to 118, the
catch block sets saveState to FAILED and saveError but never returns the button
to an actionable state; change the catch to set saveState:
SaveButton.States.FAILED and saveError as now, then schedule a follow-up
setState (use the same delay used on the success path) that resets saveState
back to SaveButton.States.WAITING and clears saveError so the SaveButton
re-enables its onClick and the user can retry.

Comment on lines +162 to +164
saveSource(fileName, data) {
return this.apiRequest('POST', `scripts/${fileName}`, { data }, { useMasterKey: true });
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep the Cloud Code cache in sync after save.

getSource() serves latestRelease.files[fileName].source when it’s already populated, so after a successful saveSource() the dashboard will still hand back the pre-save source until the minute-long cache expires. Please update (or invalidate) the cached entry once the POST resolves so users don’t reopen the file and see stale code despite a green “Saved!”.

Apply this diff to update the cache eagerly:

-  saveSource(fileName, data) {
-    return this.apiRequest('POST', `scripts/${fileName}`, { data }, { useMasterKey: true });
-  }
+  saveSource(fileName, data) {
+    return this.apiRequest('POST', `scripts/${fileName}`, { data }, { useMasterKey: true }).then(
+      result => {
+        if (this.latestRelease.files && this.latestRelease.files[fileName]) {
+          this.latestRelease.files[fileName].source = data;
+        }
+        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
saveSource(fileName, data) {
return this.apiRequest('POST', `scripts/${fileName}`, { data }, { useMasterKey: true });
}
saveSource(fileName, data) {
return this.apiRequest('POST', `scripts/${fileName}`, { data }, { useMasterKey: true })
.then(result => {
if (this.latestRelease.files && this.latestRelease.files[fileName]) {
this.latestRelease.files[fileName].source = data;
}
return result;
});
}
🤖 Prompt for AI Agents
In src/lib/ParseApp.js around lines 162-164, saveSource currently posts the new
script but does not update the in-memory Cloud Code cache, so getSource can
return stale content; change saveSource to update (or invalidate)
latestRelease.files[fileName].source after the POST resolves: call the
apiRequest, then on success check this.latestRelease and
this.latestRelease.files and either set
this.latestRelease.files[fileName].source = data (or delete the cached entry) so
the cache reflects the newly saved content; ensure you only mutate when the POST
succeeds and guard for missing objects.

@R3D347HR4Y
Copy link

This is still an issue that needs fixing!

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.

Access to Push > Create an audience returns 404
3 participants