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

Add a new query panel #2393

Merged
merged 147 commits into from
Dec 20, 2023
Merged

Conversation

bharatkashyap
Copy link
Member

@bharatkashyap bharatkashyap commented Jul 30, 2023

Closes: #2138

Screen.Recording.2023-10-24.at.8.33.57.PM.mov
  • I've read and followed the contributing guide on how to create great pull requests.
  • I've updated the relevant documentation for any new or updated feature
  • I've linked relevant GitHub issue with "Closes #"
  • I've added a visual demonstration under the form of a screenshot or video

TODO

  • https://excalidraw.com/#json=AQ-1k8DkwEVa2RUtGCQMG,TwZB3D-vbIgvCysL-mJ8mw

  • Mode: Add a label in the explorer to distinguish automatic and manual queries and create separate buttons for creating them

  • Settings: Either use tabs, or a settings cog icon which switches the display to settings, the icon switching to close. Explorer either experience and choose one.

  • Response tabs: The preview should be one tab in a set which could include the network response etc. in a vertically expandable section.

  • Scrolling: Panel should be expandable vertically.

  • Tests: Edit tests to adapt to query panel

  • Saving: Bunch changes to the queries and apply via Ctrl/Cmd + S.

  • Unsaved changes: Detect click-away from the query tab and show a dialog to manage unsaved changes; Show an unsaved changes indicator on query tab

  • Order of panels: Pages, Page Hierarchy, Queries, Actions

  • Make Actions/Queries separate sections

  • “Save” button in the top panel, "Preview" button in the Preview/Dev Tools panel

  • When creating a new query, it immediately saves the query to the project. Instead it should create it in draft and let me save or discard it when I'm done. There shouldn't be any query in the project because that makes the runtime immediately start executing it. This is how it currently works and I believe we should keep this behavior.

  • Differentiate queries/actions in binding modal

  • Scroll to new file on “New File” click

  • Fix functions autocomplete anchor position?

  • Try and reduce the attention the sub-tabs draw

@bharatkashyap
Copy link
Member Author

Ok managed to get it to work. Cleaned out node modules and pulled the latest changes.

Some things I noticed:

Screen.Recording.2023-12-12.at.10.31.22.mov

  • Resizing doesn't work
  • When opening page parameters, the panel closes, even with unsaved changes

Fixed both of those; the resizing was a strange bug I didn't completely investigate but got around for now. The page parameters dialog I think should be allowed to coexist with an open panel, so I made that behaviour happen.

@@ -40,11 +40,11 @@ const FUTURE_COMPONENTS = new Map<string, FutureComponentSpec>([
['Radio', { url: 'https://github.com/mui/mui-toolpad/issues/744', displayName: 'Radio' }],
]);

const WIDTH_COLLAPSED = 40;
export const COMPONENT_CATALOG_WIDTH_COLLAPSED = 40;
Copy link
Member

@Janpot Janpot Dec 14, 2023

Choose a reason for hiding this comment

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

No need to export.

Suggested change
export const COMPONENT_CATALOG_WIDTH_COLLAPSED = 40;
const COMPONENT_CATALOG_WIDTH_COLLAPSED = 40;

@@ -73,15 +75,21 @@ const EditableText = React.forwardRef<HTMLInputElement, EditableTextProps>(

const handleChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
if (readOnly) {
Copy link
Member

@Janpot Janpot Dec 14, 2023

Choose a reason for hiding this comment

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

If this gets called while the input is readonly, that would mean there is an error in the code, right?
In that case, would it make more sense to make this an invariant?

Suggested change
if (readOnly) {
invariant(!readOnly, 'Readonly input should be disabled')

(Same below)

Copy link
Member

@Janpot Janpot left a comment

Choose a reason for hiding this comment

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

Went through all the files. This looks great! I sprinkled a few comments here and there, nothing blocking.

@@ -563,8 +563,12 @@ function parseBindings(
}

if (appDom.isQuery(elm)) {
let kind: 'query' | 'action' = 'query';
Copy link
Member

Choose a reason for hiding this comment

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

Remark Is there value in introducing another name for "mutation"? We could also keep calling it "mutation" in the code and just use "Action" as a display name? It could keep the code simpler.

I'm marking this with Remark. It's just a stylistic comment on the code. These are just things that go through my head when reading the code. Doesn't require a change.

Copy link
Member Author

Choose a reason for hiding this comment

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

Understood, I added "action" as a way to progressively perhaps do away with mutation in the code and eventually create a separate node type called "action", since if that is what we want to call it in the UI, should it also not be what we describe it in the codebase for simplicity?

Copy link
Member

@Janpot Janpot Dec 14, 2023

Choose a reason for hiding this comment

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

Personally I prefer either one way or the other:

  • either we call it "action" everywhere and just do a translation to "mutation", right before we read/write to a file
  • or we call it "mutation" everywhere and name it "action" just right before we display in the UI

The reason is that this prevents us from mismatching and constant translation all around the codebase. Ideally if the rename is done in a large PR, you start with the latter, just to avoid introducing many unnecessary chamges to unrelated code. Then in a separate PR update the codebase to the former.

But, leave it for now, I marked it as a "note" it's not blocking the merge of this PR.

onSelectNode?.(toolpadNodeId);
}, [onSelectNode, toolpadNodeId]);

const queryCreationMode = React.useMemo(() => {
Copy link
Member

Choose a reason for hiding this comment

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

Note Ideally, the DataTreeItem doesn't know if it's a query or a mutation. In a true abstraction, we should be able to introduce a third query type without needing to alter DataTreeItem.

Also, I don't fully understand, do all tree nodes in the queries tree have the same node id? ids must be unique, otherwise we can't refer to the individual items in the treeview APIs. And in an ideal world they are opaque as well. (Meaning: we can't gain any knowledge about the object just by inspecting the id on its own)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ideally, the DataTreeItem doesn't know if it's a query or a mutation. In a true abstraction, we should be able to introduce a third query type without needing to alter DataTreeItem.

Agreed, naming it DataTreeItem was an attempt to begin making it agnostic to the type of item represented but I imagine that'll have to be a separate effort distinct from this one or any future improvements scoped to the query panel

Also, I don't fully understand, do all tree nodes in the queries tree have the same node id? ids must be unique, otherwise we can't refer to the individual items in the treeview APIs. And in an ideal world they are opaque as well. (Meaning: we can't gain any knowledge about the object just by inspecting the id on its own)

The node ids are separate for each node

import { BindableAttrValue, LiveBinding } from '@mui/toolpad-core';
import { useBrowserJsRuntime } from '@mui/toolpad-core/jsBrowserRuntime';
import invariant from 'invariant';

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change

}}
>
<Stack
display={'grid'}
Copy link
Member

Choose a reason for hiding this comment

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

Looking for a lint rule that can catch these

Suggested change
display={'grid'}
display="grid"

value={draft?.attributes?.mode ?? 'query'}
onChange={handleModeChange}
sx={{
'& .MuiInputLabel-root': { fontSize: 12 },
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'& .MuiInputLabel-root': { fontSize: 12 },
[`& .${inputLabelClasses.root}`]: { fontSize: 12 },

?

Copy link
Member

Choose a reason for hiding this comment

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

I noticed this all around the PR, but I only mentioned it once.
Non-blocking as well

@@ -81,7 +81,7 @@ function QueryEditor({

const handleSpreadsheetChange = React.useCallback(
(event: React.SyntheticEvent<Element, Event>, newValue: GoogleDriveFile | null) => {
setInput((existing) => {
setInput?.((existing) => {
Copy link
Member

Choose a reason for hiding this comment

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

Why did this change? What is the purpose of a query editor without an onChange prop defined?

Copy link
Member Author

Choose a reason for hiding this comment

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

The onChange functionality for the new query editor client started living in the appStateApi, so we don't need to pass in an onChange handler prop anymore

Copy link
Member

Choose a reason for hiding this comment

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

But functionally, how should it behave when no handler is defined. What's the use-case?

Copy link
Member

Choose a reason for hiding this comment

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

The function selection control belongs in its own file.

@@ -1,8 +1,9 @@
import { BindableAttrValue, ExecFetchResult, PrimitiveValueType } from '@mui/toolpad-core';
import type { IntrospectionResult } from '../../server/functionsTypesWorker';

export interface LocalConnectionParams {}
export type FileTypes = 'query' | 'component' | 'page' | 'theme';
Copy link
Member

Choose a reason for hiding this comment

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

Is this being used?

Suggested change
export type FileTypes = 'query' | 'component' | 'page' | 'theme';

@@ -12,6 +12,7 @@ function MySQLConnectionParamsInput({

const dataSource: ClientDataSource<SqlConnectionParams, SqlQuery> = {
displayName: 'MySQL',
isEnabled: false,
Copy link
Member

Choose a reason for hiding this comment

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

Instead of this property, why not just omit them where we're exporting all the datasources?

in any case, everywhere in the codebase "enabled"/"disabled" seem one of this names where the "is" prefix is always implied.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, this can be done without

@@ -18,6 +18,8 @@ export const TOOLPAD_BRIDGE_GLOBAL = '__TOOLPAD_BRIDGE__';

export const VERSION_CHECK_INTERVAL = 1000 * 60 * 10;

export const PAGE_PANEL_WIDTH = 250;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export const PAGE_PANEL_WIDTH = 250;

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you mean directly defining the width in the consuming file instead of creating a constant for this?

Copy link
Member

@Janpot Janpot Dec 14, 2023

Choose a reason for hiding this comment

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

Yes, it's still a constant, but no need for it globally. It's used in only one file. And it's the sort of thing that probably should always be local.

if (readOnly) {
return;
}
invariant(!readOnly, 'Readonly input should be disabled');
Copy link
Member

Choose a reason for hiding this comment

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

There's another instance in this file where this is happening

@@ -29,25 +41,16 @@ export default function QueryIcon({ id: iconId, mode, sx }: QueryIconProps) {
const ModeIcon = modeIconMap.get(mode ?? '');

return (
<div style={{ display: 'flex' }}>
<div style={{ display: 'flex', gap: DataSourceIcon ? 0.5 : 0 }}>
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<div style={{ display: 'flex', gap: DataSourceIcon ? 0.5 : 0 }}>
<div style={{ display: 'flex', gap: 0.5 }}>

The gap has no effect if there is only one child

@bharatkashyap bharatkashyap mentioned this pull request Dec 14, 2023
5 tasks
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Dec 18, 2023
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Dec 19, 2023
@bharatkashyap bharatkashyap enabled auto-merge (squash) December 20, 2023 15:34
@bharatkashyap bharatkashyap merged commit 04a1e57 into mui:master Dec 20, 2023
2 checks passed
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.

Move queries section to the left
3 participants