Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1e707d5
feat: add custom views page
mtrezza Jul 10, 2025
5f61237
chore: address lint issues
mtrezza Jul 10, 2025
eab8cf3
fix: populate classes in Views dialog
mtrezza Jul 10, 2025
d485379
fix: handle view query errors
mtrezza Jul 10, 2025
8825239
fix: align view table headers
mtrezza Jul 10, 2025
49cd9fc
feat: link pointers in views
mtrezza Jul 10, 2025
6ed37ba
fix: preserve filter URL for pointer links
mtrezza Jul 10, 2025
bb8eabd
fix: improve views table usability
mtrezza Jul 10, 2025
e97fbd9
fix: sync views column widths
mtrezza Jul 10, 2025
1de7b4c
fix: align resizable view columns
mtrezza Jul 10, 2025
5781c2b
fix: align view headers with columns
mtrezza Jul 10, 2025
c0ca3e2
fix: stabilize column resize
mtrezza Jul 10, 2025
0404b3e
fix: sync view headers with scroll
mtrezza Jul 10, 2025
1708ad9
fix: allow route for views page
mtrezza Jul 10, 2025
d490260
fix: parse route query string
mtrezza Jul 11, 2025
7c6230a
Revert "fix: sync view headers with scroll"
mtrezza Jul 11, 2025
510dea2
Revert "fix: allow route for views page"
mtrezza Jul 11, 2025
d18cb6a
Revert "fix: parse route query string"
mtrezza Jul 11, 2025
80ef8c1
feat: Allow editing views (#2889)
mtrezza Jul 11, 2025
eef72bd
fix: Prevent stale pointer navigation (#2890)
mtrezza Jul 12, 2025
146ec5d
fix: Handle invalid pointers in Views results (#2891)
mtrezza Jul 12, 2025
5b96bea
Update TableView.scss
mtrezza Jul 12, 2025
f9514dd
Cell height fix
mtrezza Jul 12, 2025
dc36630
fix: Center pill in Views table cells (#2895)
mtrezza Jul 12, 2025
d74b4f8
docs: Add Views feature (#2896)
mtrezza Jul 12, 2025
d657be1
fix: Display ISO string for date objects in views (#2897)
mtrezza Jul 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/dashboard/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import SlowQueries from './Analytics/SlowQueries/SlowQueries.react';
import styles from 'dashboard/Apps/AppsIndex.scss';
import UsersSettings from './Settings/UsersSettings.react';
import Webhooks from './Data/Webhooks/Webhooks.react';
import Views from './Data/Views/Views.react';
import { AsyncStatus } from 'lib/Constants';
import baseStyles from 'stylesheets/base.scss';
import { get } from 'lib/AJAX';
Expand Down Expand Up @@ -270,6 +271,8 @@ export default class Dashboard extends React.Component {

<Route path="cloud_code" element={<CloudCode />} />
<Route path="cloud_code/*" element={<CloudCode />} />
<Route path="views/:name" element={<Views />} />
<Route path="views" element={<Views />} />
<Route path="webhooks" element={<Webhooks />} />

<Route path="jobs">{JobsRoute}</Route>
Expand Down
5 changes: 5 additions & 0 deletions src/dashboard/DashboardView.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ export default class DashboardView extends React.Component {
});
}

coreSubsections.push({
name: 'Views',
link: '/views',
});

//webhooks requires removal of heroku link code, then it should work.
if (
features.hooks &&
Expand Down
111 changes: 111 additions & 0 deletions src/dashboard/Data/Views/CreateViewDialog.react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import Dropdown from 'components/Dropdown/Dropdown.react';
import Field from 'components/Field/Field.react';
import Label from 'components/Label/Label.react';
import Modal from 'components/Modal/Modal.react';
import Option from 'components/Dropdown/Option.react';
import React from 'react';
import TextInput from 'components/TextInput/TextInput.react';
import Checkbox from 'components/Checkbox/Checkbox.react';

function isValidJSON(value) {
try {
JSON.parse(value);
return true;
} catch {
return false;
}
}

export default class CreateViewDialog extends React.Component {
constructor() {
super();
this.state = {
name: '',
className: '',
query: '[]',
showCounter: false,
};
}

valid() {
return (
this.state.name.length > 0 &&
this.state.className.length > 0 &&
isValidJSON(this.state.query)
);
}

render() {
const { classes, onConfirm, onCancel } = this.props;
return (
<Modal
type={Modal.Types.INFO}
icon="plus"
iconSize={40}
title="Create a new view?"
subtitle="Define a custom query to display data."
confirmText="Create"
cancelText="Cancel"
disabled={!this.valid()}
onCancel={onCancel}
onConfirm={() =>
onConfirm({
name: this.state.name,
className: this.state.className,
query: JSON.parse(this.state.query),
showCounter: this.state.showCounter,
})
}
>
<Field
label={<Label text="Name" />}
input={
<TextInput
value={this.state.name}
onChange={name => this.setState({ name })}
/>
}
/>
<Field
label={<Label text="Class" />}
input={
<Dropdown
value={this.state.className}
onChange={className => this.setState({ className })}
>
{classes.map(c => (
<Option key={c} value={c}>
{c}
</Option>
))}
</Dropdown>
}
/>
<Field
label={
<Label
text="Query"
description="An aggregation pipeline that returns an array of items."
/>
}
input={
<TextInput
multiline={true}
value={this.state.query}
onChange={query => this.setState({ query })}
/>
}
/>
<Field
label={<Label text="Show object counter" />}
input={
<Checkbox
checked={this.state.showCounter}
onChange={showCounter => this.setState({ showCounter })}
/>
}
/>
</Modal>
);
}
}
45 changes: 45 additions & 0 deletions src/dashboard/Data/Views/DeleteViewDialog.react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Field from 'components/Field/Field.react';
import Label from 'components/Label/Label.react';
import Modal from 'components/Modal/Modal.react';
import React from 'react';
import TextInput from 'components/TextInput/TextInput.react';

export default class DeleteViewDialog extends React.Component {
constructor() {
super();
this.state = {
confirmation: '',
};
}

valid() {
return this.state.confirmation === this.props.name;
}

render() {
return (
<Modal
type={Modal.Types.DANGER}
icon="warn-outline"
title="Delete view?"
subtitle="This action cannot be undone!"
disabled={!this.valid()}
confirmText="Delete"
cancelText="Cancel"
onCancel={this.props.onCancel}
onConfirm={this.props.onConfirm}
>
<Field
label={<Label text="Confirm this action" description="Enter the view name to continue." />}
input={
<TextInput
placeholder="View name"
value={this.state.confirmation}
onChange={confirmation => this.setState({ confirmation })}
/>
}
/>
</Modal>
);
}
}
Comment on lines +7 to +45
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add prop validation for required props.

The component expects name, onCancel, and onConfirm props but doesn't validate them. Consider adding PropTypes validation to ensure proper usage.

Add prop validation after the component class:

DeleteViewDialog.propTypes = {
  name: PropTypes.string.isRequired,
  onCancel: PropTypes.func.isRequired,
  onConfirm: PropTypes.func.isRequired,
};

And import PropTypes at the top:

+import PropTypes from 'prop-types';
import Field from 'components/Field/Field.react';
🤖 Prompt for AI Agents
In src/dashboard/Data/Views/DeleteViewDialog.react.js from lines 7 to 45, the
component uses props name, onCancel, and onConfirm but lacks prop validation. To
fix this, import PropTypes at the top of the file and add a static propTypes
object or assign DeleteViewDialog.propTypes after the class definition,
specifying name as a required string and onCancel and onConfirm as required
functions to ensure proper prop usage and catch errors early.

112 changes: 112 additions & 0 deletions src/dashboard/Data/Views/EditViewDialog.react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import Dropdown from 'components/Dropdown/Dropdown.react';
import Field from 'components/Field/Field.react';
import Label from 'components/Label/Label.react';
import Modal from 'components/Modal/Modal.react';
import Option from 'components/Dropdown/Option.react';
import React from 'react';
import TextInput from 'components/TextInput/TextInput.react';
import Checkbox from 'components/Checkbox/Checkbox.react';

function isValidJSON(value) {
try {
JSON.parse(value);
return true;
} catch {
return false;
}
}
Comment on lines +10 to +17
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract isValidJSON to a shared utility.

This function is duplicated from src/dashboard/Push/PushNew.react.js. Consider extracting it to a shared utility module to follow DRY principles.

Create a new utility file src/lib/jsonUtils.js:

export function isValidJSON(value) {
  try {
    JSON.parse(value);
    return true;
  } catch {
    return false;
  }
}

Then import and use it in both files:

-function isValidJSON(value) {
-  try {
-    JSON.parse(value);
-    return true;
-  } catch {
-    return false;
-  }
-}
+import { isValidJSON } from 'lib/jsonUtils';
🤖 Prompt for AI Agents
In src/dashboard/Data/Views/EditViewDialog.react.js lines 10 to 17, the
isValidJSON function is duplicated from src/dashboard/Push/PushNew.react.js.
Extract this function into a new shared utility file at src/lib/jsonUtils.js by
moving the function there and exporting it. Then, replace the local isValidJSON
function in both files with an import statement that imports isValidJSON from
the new utility module.


export default class EditViewDialog extends React.Component {
constructor(props) {
super();
const view = props.view || {};
this.state = {
name: view.name || '',
className: view.className || '',
query: JSON.stringify(view.query || [], null, 2),
showCounter: !!view.showCounter,
};
}

valid() {
return (
this.state.name.length > 0 &&
this.state.className.length > 0 &&
isValidJSON(this.state.query)
);
}

render() {
const { classes, onConfirm, onCancel } = this.props;
return (
<Modal
type={Modal.Types.INFO}
icon="edit-solid"
iconSize={40}
title="Edit view?"
subtitle="Update the custom query."
confirmText="Save"
cancelText="Cancel"
disabled={!this.valid()}
onCancel={onCancel}
onConfirm={() =>
onConfirm({
name: this.state.name,
className: this.state.className,
query: JSON.parse(this.state.query),
showCounter: this.state.showCounter,
})
}
>
<Field
label={<Label text="Name" />}
input={
<TextInput
value={this.state.name}
onChange={name => this.setState({ name })}
/>
}
/>
<Field
label={<Label text="Class" />}
input={
<Dropdown
value={this.state.className}
onChange={className => this.setState({ className })}
>
{classes.map(c => (
<Option key={c} value={c}>
{c}
</Option>
))}
</Dropdown>
}
/>
<Field
label={
<Label
text="Query"
description="An aggregation pipeline that returns an array of items."
/>
}
input={
<TextInput
multiline={true}
value={this.state.query}
onChange={query => this.setState({ query })}
/>
}
/>
<Field
label={<Label text="Show object counter" />}
input={
<Checkbox
checked={this.state.showCounter}
onChange={showCounter => this.setState({ showCounter })}
/>
}
/>
</Modal>
);
}
}
Comment on lines +19 to +112
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add prop validation for component props.

The component expects several props but doesn't validate them.

Add prop validation after the component class:

EditViewDialog.propTypes = {
  classes: PropTypes.arrayOf(PropTypes.string).isRequired,
  view: PropTypes.shape({
    name: PropTypes.string,
    className: PropTypes.string,
    query: PropTypes.array,
    showCounter: PropTypes.bool,
  }),
  onCancel: PropTypes.func.isRequired,
  onConfirm: PropTypes.func.isRequired,
};

And import PropTypes:

+import PropTypes from 'prop-types';
import Dropdown from 'components/Dropdown/Dropdown.react';
🤖 Prompt for AI Agents
In src/dashboard/Data/Views/EditViewDialog.react.js from lines 19 to 112, the
EditViewDialog component lacks prop validation which can lead to runtime errors.
To fix this, import PropTypes at the top of the file and add a static propTypes
object after the component class definition specifying the expected types for
classes, view, onCancel, and onConfirm props as described. This ensures proper
validation and better maintainability.

Loading
Loading