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

custom dashboard #193

Draft
wants to merge 5 commits into
base: coderabbit_micro_frontend
Choose a base branch
from
Draft

Conversation

GurinderRawala
Copy link

No description provided.

@GurinderRawala GurinderRawala self-assigned this Nov 29, 2024
Copy link

coderabbitai bot commented Nov 29, 2024

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes in this pull request introduce several enhancements across multiple components and services in the application. Notable updates include the addition of new properties and methods to facilitate dashboard customization, improvements in state management, and refinements in component rendering logic. The LocationService interface now supports direct manipulation of pathnames and query parameters. Other components, such as DashboardGrid and FnAppProvider, have been updated to incorporate new features and improve their responsiveness to state changes, while maintaining backward compatibility.

Changes

File Path Change Summary
packages/grafana-runtime/src/services/LocationService.tsx Added method fnPathnameChange(path: string, queryParams: any) to LocationService interface and implemented it in HistoryWrapper.
public/app/core/components/Page/Page.tsx Refactored getStyles function to simplify style application; no changes to exported entities.
public/app/core/reducers/fn-slice.ts Added property isCustomDashboard: boolean to FnGlobalState interface and initialized it in INITIAL_FN_STATE.
public/app/features/dashboard/components/AddPanelButton/AddPanelButton.tsx Added optional property isFNDashboard to Props interface; updated button size and variant based on this property.
public/app/features/dashboard/containers/DashboardPage.tsx Updated MapStateToDashboardPageProps to include isCustomDashboard; changed scrollElement type in state interface.
public/app/features/dashboard/dashgrid/DashboardGrid.tsx Renamed class from Component to DashboardGridUnconnected; added panelFilter logic and isCustomDashboard prop.
public/app/features/dashboard/dashgrid/DashboardPanel.tsx Updated onInstanceStateChange method to use unknown type for value parameter.
public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx Modified mapStateToProps to conditionally set isFnDashboard based on isCustomDashboard.
public/app/fn-app/create-mfe.ts Updated updateFnApp method to include isCustomDashboard; refined error handling in mountFnApp.
public/app/fn-app/fn-app-provider.tsx Added command palette feature; updated component structure to include KBarProvider and routing enhancements.
public/app/fn-app/fn-dashboard-page/render-fn-dashboard.tsx Changed history property in DEFAULT_DASHBOARD_PAGE_PROPS to use locationService.getHistory().
public/app/fn-app/types.ts Added optional property isCustomDashboard to FNDashboardProps interface.

Poem

In the land of code where rabbits play,
New paths and features brighten the day.
With dashboards custom, and styles refined,
Every change brings joy, oh so kind!
Hopping through functions, we cheer with glee,
For a better tomorrow, just wait and see! 🐇✨


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.

@GurinderRawala GurinderRawala changed the title custom dashboard checkpoint custom dashboard Nov 29, 2024
@GurinderRawala GurinderRawala marked this pull request as draft November 29, 2024 17:58
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: 11

🧹 Outside diff range and nitpick comments (24)
public/app/fn-app/types.ts (1)

33-33: Consider adding JSDoc documentation.

To improve maintainability, consider adding JSDoc comments to document the purpose and usage of the isCustomDashboard property.

+  /** Indicates whether this dashboard is customizable by the user */
   isCustomDashboard?: boolean;
public/app/features/dashboard/components/AddPanelButton/AddPanelButton.tsx (1)

13-13: Document the purpose of isFNDashboard

The purpose and meaning of "FN" in isFNDashboard is not immediately clear. Consider adding a JSDoc comment to explain its purpose and when it should be used.

+  /** Flag indicating if this is a Function Navigator dashboard, which affects button styling */
   isFNDashboard?: boolean;
public/app/core/reducers/fn-slice.ts (1)

52-52: Document the purpose of isCustomDashboard

Consider adding a comment explaining the purpose and implications of this flag, similar to the existing comment for FNDashboard.

  FNDashboard: false,
+ // NOTE: Controls whether the dashboard is customizable by the user
  isCustomDashboard: false,
public/app/fn-app/fn-dashboard-page/render-fn-dashboard.tsx (3)

Line range hint 47-51: Consider memoizing the pathname value

The pathname change effect might trigger unnecessary updates if window.location.pathname hasn't changed. Consider memoizing the pathname or adding it to the dependency array.

  useEffect(() => {
+   const currentPathname = window.location.pathname;
-   locationService.fnPathnameChange(window.location.pathname, queryParams);
+   locationService.fnPathnameChange(currentPathname, queryParams);
-  }, [queryParams]);
+  }, [queryParams, window.location.pathname]);

Line range hint 53-69: Remove redundant spread of DEFAULT_DASHBOARD_PAGE_PROPS

The current implementation spreads DEFAULT_DASHBOARD_PAGE_PROPS twice, which is unnecessary and could impact performance.

  const dashboardPageProps: DashboardPageProps = useMemo(
    () =>
-     merge({}, DEFAULT_DASHBOARD_PAGE_PROPS, {
-       ...DEFAULT_DASHBOARD_PAGE_PROPS,
+     merge({}, DEFAULT_DASHBOARD_PAGE_PROPS, {
        match: {
          params: {
            ...props,
          },
        },
        location: locationService.getLocation(),
        queryParams,
        hiddenVariables,
        controlsContainer,
        isLoading,
      }),
    [controlsContainer, hiddenVariables, isLoading, props, queryParams]
  );

isCustomDashboard prop is already part of the interface but not used in the component

The verification reveals that:

  • isCustomDashboard is properly defined in the FNDashboardProps interface in public/app/fn-app/types.ts
  • The component RenderFNDashboard receives the prop through its type definition but doesn't utilize it in its implementation
  • The property is actively used throughout the codebase, particularly in DashboardPage.tsx and DashboardGrid.tsx for controlling dashboard behavior

The component should handle this prop as it's a crucial part of the dashboard functionality, affecting editability and rendering behavior.

🔗 Analysis chain

Line range hint 29-29: Verify custom dashboard props handling

The component receives FNDashboardProps but should also handle the new isCustomDashboard property mentioned in the summary.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify custom dashboard property handling

# Check FNDashboardProps interface definition
rg -A 10 "interface FNDashboardProps"

# Look for isCustomDashboard usage in related files
rg "isCustomDashboard.*DashboardPage"

Length of output: 697


Script:

#!/bin/bash
# Let's check for any isCustomDashboard property usage in the codebase
rg "isCustomDashboard" -A 3

# Also check the component implementation for any custom dashboard related logic
rg -A 10 "RenderFNDashboard.*=.*props" 

Length of output: 7846

public/app/features/dashboard/dashgrid/DashboardPanel.tsx (1)

Line range hint 1-140: Consider modernizing to functional component in future

While the current implementation is stable and working well, consider gradually modernizing this component to use functional components with hooks in future iterations. This would align with current React best practices and potentially simplify the state management code.

packages/grafana-runtime/src/services/LocationService.tsx (1)

Line range hint 24-51: Consider architectural implications of location service changes

While adding custom dashboard functionality, modifying core navigation services requires careful consideration:

  1. The LocationService is a fundamental service used throughout Grafana
  2. Changes here could affect all navigation flows, not just dashboard-related ones
  3. Direct manipulation of history state could interfere with Grafana's routing system

Consider these alternatives:

  • Use existing methods like partial() or push() instead of adding a new method
  • If a new method is needed, implement it at the dashboard service level instead
  • Document the specific use case this solves in the custom dashboard feature
public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx (1)

621-621: Consider improving readability with a descriptive variable name

The boolean expression could be more readable by extracting it into a descriptive variable name.

-    isFnDashboard: state.fnGlobalState.FNDashboard && !state.fnGlobalState.isCustomDashboard,
+    const isStandardFunctionDashboard = state.fnGlobalState.FNDashboard && !state.fnGlobalState.isCustomDashboard;
+    isFnDashboard: isStandardFunctionDashboard,
public/app/features/dashboard/dashgrid/DashboardGrid.tsx (2)

246-252: Simplify panel rendering logic when applying panel filters

The conditional statements for adding panels to panelElements can be simplified to improve readability.

Apply this diff to streamline the condition:

if (!panelFilter) {
  panelElements.push(p);
} else {
-  if (panelFilter.test(panel.title)) {
-    panelElements.push(p);
-  }
+  if (panelFilter.test(panel.title)) {
+    panelElements.push(p);
+  }
}

Or even better, combine the conditions:

- if (!panelFilter) {
-   panelElements.push(p);
- } else {
-   if (panelFilter.test(panel.title)) {
-     panelElements.push(p);
-   }
- }
+ if (!panelFilter || panelFilter.test(panel.title)) {
+   panelElements.push(p);
+ }

340-341: Review conditional logic for draggable and resizable properties

The conditions for isDraggable and isResizable may be complex and could benefit from simplification for better readability.

Consider simplifying the ternary operators:

<ReactGridLayout
  width={width}
- isDraggable={isFnDashboard && !isCustomDashboard ? false : draggable}
- isResizable={isFnDashboard && !isCustomDashboard ? false : isEditable}
+ isDraggable={!isFnDashboard || isCustomDashboard ? draggable : false}
+ isResizable={!isFnDashboard || isCustomDashboard ? isEditable : false}
  // ... other props
/>

This may make the conditions easier to understand.

public/app/features/dashboard/containers/DashboardPage.tsx (13)

3-3: Remove unused import of PureComponent

The PureComponent imported from 'react' is not used in the file and can be removed to clean up the code.

Apply this diff to remove the unused import:

-import { PureComponent } from 'react';

16-16: Consider lazy loading internationalization strings

If the t function from 'app/core/internationalization' is not heavily used, consider lazy loading it to improve performance.


22-22: Organize imports for better readability

Group related imports together and order them logically to enhance code readability.


80-80: Consistent use of TypeScript utility types

Consider using Pick or Partial consistently when extracting types from FnGlobalState to maintain uniform code style.


101-102: Maintain alphabetical order in mapStateToProps

For better readability and maintainability, consider arranging the properties in mapStateToProps in alphabetical order.


137-137: Ensure proper declaration of scrollElement in state

The optional property scrollElement of type ScrollRefElement in the component state should be initialized appropriately to avoid potential undefined references.


161-161: Destructure props consistently

For consistency, destructure props parameters in a consistent manner throughout the component.


201-201: Include all necessary props in destructuring

Ensure that all used props are included when destructuring to avoid potential undefined values.


Line range hint 207-220: Handle potential infinite loops in componentDidUpdate

The componentDidUpdate method re-initializes the dashboard when certain conditions are met, which could lead to an infinite update loop if not carefully managed.

Ensure that the initDashboard call and state updates do not cause repeated re-renders.


387-387: Destructure additional props for clarity

Consider destructuring initPhase from this.props for consistency and clarity.


400-400: Simplify the logic for isFNDashboardEditable

The assignment of isFNDashboardEditable can be simplified for better readability.

Simplify the conditional logic:

-const isFNDashboardEditable = (isCustomDashboard && FNDashboard) || !FNDashboard;
+const isFNDashboardEditable = isCustomDashboard && FNDashboard ? true : !FNDashboard;

452-453: Consider extracting inline styles to a CSS class

For better maintainability, move the inline styles into a dedicated CSS class instead of using inline styling.

Create a CSS class and apply it to the <div> element.

.dashboard-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 4px;
}

Then update the code:

-<div
-  style={{
-    display: 'flex',
-    justifyContent: 'flex-end',
-    gap: 4,
-  }}
->
+<div className="dashboard-toolbar">

Line range hint 534-541: Avoid side effects in getDerivedStateFromProps

Using functions like updateStatePageNavFromProps that may cause side effects within getDerivedStateFromProps can lead to unexpected behavior. Ensure that this method is pure and only computes and returns new state.

Refactor updateStatePageNavFromProps to be a pure function and avoid mutating the state directly.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f633be4 and e7c30a8.

📒 Files selected for processing (12)
  • packages/grafana-runtime/src/services/LocationService.tsx (1 hunks)
  • public/app/core/components/Page/Page.tsx (1 hunks)
  • public/app/core/reducers/fn-slice.ts (2 hunks)
  • public/app/features/dashboard/components/AddPanelButton/AddPanelButton.tsx (2 hunks)
  • public/app/features/dashboard/containers/DashboardPage.tsx (14 hunks)
  • public/app/features/dashboard/dashgrid/DashboardGrid.tsx (11 hunks)
  • public/app/features/dashboard/dashgrid/DashboardPanel.tsx (1 hunks)
  • public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx (1 hunks)
  • public/app/fn-app/create-mfe.ts (1 hunks)
  • public/app/fn-app/fn-app-provider.tsx (3 hunks)
  • public/app/fn-app/fn-dashboard-page/render-fn-dashboard.tsx (1 hunks)
  • public/app/fn-app/types.ts (1 hunks)
🔇 Additional comments (18)
public/app/fn-app/types.ts (1)

33-33: LGTM! The new property is well-implemented.

The isCustomDashboard optional boolean property is correctly typed and maintains backward compatibility.

public/app/features/dashboard/components/AddPanelButton/AddPanelButton.tsx (2)

16-16: LGTM!

The component signature is correctly updated to include the new prop while maintaining backward compatibility.


33-34: Verify design consistency for button styling

The button now has different sizes based on dashboard type (md for FN dashboards, sm for others) and uses the primary variant. This might create visual inconsistency across different dashboard views.

Let's check if this is consistent with other similar buttons in the codebase:

✅ Verification successful

Button styling is consistent with existing patterns

The verification shows that the button styling follows established patterns in the dashboard components:

  • The ShareButton component in DashNav also uses variant="primary" with size="sm", matching our non-FN dashboard case
  • Primary variant is commonly used for important actions across dashboard components (share, apply, save, etc.)
  • The size variation based on context is not unprecedented, as seen in PanelEditor where size is also conditionally set
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other Button components with size props to verify consistency
ast-grep --pattern 'Button $$$size={$_}$$$'

# Search for other primary variant buttons in dashboard components
rg -A 2 "variant=\"primary\"" "public/app/features/dashboard"

Length of output: 6143

public/app/core/reducers/fn-slice.ts (2)

9-9: Consider adding isCustomDashboard to mapped props types

The new property is not included in FnPropMappedFromState type. If components need to access this state via prop mapping, consider adding it to the mapped props.


Line range hint 65-75: Verify permission checks for custom dashboard state changes

The reducers allow unrestricted updates to isCustomDashboard. Since this property affects dashboard editing permissions (as mentioned in the PR summary), ensure that appropriate permission checks are implemented in the components that dispatch these actions.

public/app/fn-app/fn-dashboard-page/render-fn-dashboard.tsx (1)

20-20: Verify locationService initialization timing

The change from type assertion to locationService.getHistory() is more robust, but we should ensure that locationService is properly initialized when this code runs.

✅ Verification successful

Based on the gathered information, I can now provide a final response regarding the locationService initialization concern:

locationService is properly initialized and safe to use

The locationService is a singleton that's automatically initialized when imported from @grafana/runtime. It's implemented as a HistoryWrapper that's instantiated with browser history by default. The service is initialized before any components are rendered, making it safe to use getHistory() and other methods.

Key findings:

  • locationService is created as a singleton with new HistoryWrapper() on import
  • HistoryWrapper safely handles history initialization for both browser and test environments
  • The service can only be overridden in test environments via setLocationService()
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if locationService initialization is handled properly
# Look for locationService initialization and potential race conditions

# Search for locationService initialization
rg -A 5 "locationService.*init|init.*locationService"

# Check if there are any error handlers for locationService
rg "catch.*locationService|locationService.*error"

Length of output: 1205


Script:

#!/bin/bash
# Let's check how locationService is used and initialized in the codebase

# Check for locationService imports and its usage pattern
rg -A 3 "import.*locationService|locationService.*from"

# Look for any singleton or service initialization patterns
ast-grep --pattern 'const locationService = $_'

# Check the actual service definition
fd locationService --exec cat {}

Length of output: 100471


Script:

#!/bin/bash
# Let's check how locationService is initialized and used in the runtime package
# since it appears to be the source of the service

# Check the LocationService implementation
fd LocationService.ts --exec cat {}

# Look for any initialization patterns in the main app
rg -A 5 "initLocationService|setLocationService"

Length of output: 11981

public/app/core/components/Page/Page.tsx (1)

96-102: Clean implementation of wrapper styles

The refactored style implementation is cleaner and more maintainable. Good use of object syntax and proper labeling.

packages/grafana-runtime/src/services/LocationService.tsx (1)

Line range hint 48-51: Critical: Unsafe implementation of pathname change

The current implementation has several critical issues:

  1. Directly mutating history.location is unsafe and bypasses the History API's proper update mechanisms
  2. No validation of the path parameter could lead to invalid URLs
  3. Changes won't trigger proper history events, potentially breaking React's state management
  4. Inconsistent with how other methods in this service handle location updates

Consider this safer implementation:

-  fnPathnameChange(path: string, queryParams: any) {
-    this.history.location.pathname = path;
-    this.history.location.search = urlUtil.toUrlParams(queryParams);
+  updatePathname(path: string, queryParams: UrlQueryMap) {
+    const search = urlUtil.toUrlParams(queryParams);
+    this.history.push({
+      pathname: path,
+      search,
+    });
   }

Let's verify the usage of this method across the codebase to assess the impact:

public/app/fn-app/create-mfe.ts (1)

283-283: LGTM! Verify integration with custom dashboard feature.

The addition of isCustomDashboard to the state update follows the existing pattern and is consistent with the codebase.

Let's verify the integration with the custom dashboard feature:

✅ Verification successful

Integration with custom dashboard feature is properly implemented

The verification confirms that isCustomDashboard is:

  • Correctly defined in FNDashboardProps interface
  • Initialized as false in the initial state
  • Properly integrated across key components:
    • Used in DashboardPage for conditional rendering and edit controls
    • Handled in DashboardGrid for drag-and-drop functionality
    • Correctly propagated through the Redux state

The addition of isCustomDashboard to the state update in create-mfe.ts is consistent with its usage throughout the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the custom dashboard feature integration
# Check for related components and interfaces that should handle isCustomDashboard

# Check for the property definition in interfaces
ast-grep --pattern 'interface $_ {
  $$$
  isCustomDashboard?: boolean;
  $$$
}'

# Check for usage in components
rg -A 5 'isCustomDashboard'

# Check for initialization in initial state
rg 'INITIAL_FN_STATE.*=.*{' -A 10

Length of output: 11752

public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx (1)

621-621: Verify impact on existing function dashboards

The new condition !state.fnGlobalState.isCustomDashboard changes how function dashboards are identified. This could affect existing dashboards that were previously marked as function dashboards.

✅ Verification successful

Based on the search results, I can now provide a final response about the impact of the change:

Change to isFnDashboard condition is safe and intentional

The new condition !state.fnGlobalState.isCustomDashboard in PanelStateWrapper is consistent with the existing behavior in DashboardGrid where:

  • Custom dashboards are meant to be editable even when they are function dashboards (isFnDashboard && !isCustomDashboard ? false : draggable)
  • The same logic is applied to resizing (isFnDashboard && !isCustomDashboard ? false : isEditable)

This change aligns the panel state behavior with the grid's handling of function dashboards vs custom dashboards, ensuring consistent editability rules across the dashboard.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential impact on existing dashboards
# Look for any direct usage of FNDashboard state without the isCustomDashboard check

# Search for other direct uses of FNDashboard state
rg "FNDashboard" --type ts --type tsx

# Search for components consuming isFnDashboard prop
ast-grep --pattern 'interface Props {
  $$$
  isFnDashboard?: boolean;
  $$$
}'

Length of output: 2222


Script:

#!/bin/bash
# Let's search for more context about FNDashboard and isCustomDashboard usage
# Note: Adding .tsx extension explicitly since previous rg command had issues

# Search for FNDashboard usage
rg "FNDashboard" --type-add 'tsx:*.tsx' --type tsx -A 2

# Search for isCustomDashboard usage
rg "isCustomDashboard" --type-add 'tsx:*.tsx' --type tsx -A 2

# Search for components that pass isFnDashboard prop
ast-grep --pattern '$_.$$$isFnDashboard$$$'

Length of output: 100291


Script:

#!/bin/bash
# Let's try a more focused search to understand the usage of FNDashboard and isCustomDashboard
# in the specific components that matter for this change

# Search for FNDashboard and isCustomDashboard in dashboard grid and panel components
rg "FNDashboard|isCustomDashboard" --type-add 'tsx:*.tsx' --type tsx -g "**/dashgrid/*" -g "**/dashboard/components/*" -A 2

# Look for components that consume isFnDashboard prop
ast-grep --pattern 'isFnDashboard={$_}'

Length of output: 1776

public/app/fn-app/fn-app-provider.tsx (2)

43-49: Function commandPaletteActionSelected correctly reports action selection

The newly added commandPaletteActionSelected function appropriately reports interactions when a command palette action is selected. This enhances analytics and user interaction tracking.


60-62: Ensure actions prop in KBarProvider is populated if required

Currently, the KBarProvider is initialized with an empty actions array. If the command palette is intended to have actionable items, consider populating this array to enable its functionality.

public/app/features/dashboard/dashgrid/DashboardGrid.tsx (1)

158-160: Clarify early return in onLayoutChange when panel filter is active

When a panelFilter is applied, the onLayoutChange method returns immediately, preventing layout updates. Ensure this behavior is intentional and consider adding a comment to explain why updates are skipped when filtering is active.

If this is intentional to prevent layout changes during filtering, no action is needed. If not, consider removing the early return.

public/app/features/dashboard/containers/DashboardPage.tsx (5)

11-11: Confirm the correct usage of ScrollRefElement

Ensure that the ScrollRefElement type imported from 'app/core/components/NativeScrollbar' is correctly used in the component's state and methods.


501-504: Ensure isEditable prop respects user permissions

Double-check that isEditable correctly reflects both the isFNDashboardEditable flag and the user's edit permissions (dashboard.meta.canEdit).


525-527: Confirm feature toggle and conditions for AddWidgetModal

Ensure that AddWidgetModal is rendered only when intended by verifying the conditions and feature toggles.


9-9: Verify necessity of imported components from @grafana/ui

Ensure that all imported components (Themeable2, withTheme2, ToolbarButtonRow, ToolbarButton, ModalsController) are used in the file. Remove any unused imports to improve code clarity.

Run the following script to identify unused imports:

✅ Verification successful

Let me generate additional verification to understand the actual usage context of these imports, as just counting occurrences might include the import statement itself.

This will help us see if these components are actually being used in the code beyond their import declarations.


All imported components from @grafana/ui are actively used in the code

Based on the verification results, all components are being used appropriately:

  • Themeable2 is used in the Props type definition
  • withTheme2 is used to wrap the UnthemedDashboardPage component
  • ToolbarButtonRow is used in multiple places for layout
  • ToolbarButton is used within the save dashboard functionality
  • ModalsController is used to handle the save dashboard modal
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for unused imports from @grafana/ui

imports=("Themeable2" "withTheme2" "ToolbarButtonRow" "ToolbarButton" "ModalsController")
file="public/app/features/dashboard/containers/DashboardPage.tsx"

for component in "${imports[@]}"; do
  count=$(rg --glob "$file" -c "\b$component\b")
  echo "$component: $count occurrences in the file"
done

Length of output: 2197


Script:

#!/bin/bash
# Get more context around the usage of these components
rg --glob "public/app/features/dashboard/containers/DashboardPage.tsx" -B 1 -A 1 "\b(Themeable2|withTheme2|ToolbarButtonRow|ToolbarButton|ModalsController)\b"

Length of output: 3120


Line range hint 415-421: Verify the condition for rendering EntityNotFound

Ensure that the condition dashboard.meta.dashboardNotFound && !FNDashboard correctly handles scenarios where a dashboard is not found, especially with the presence of FNDashboard.

Run the following script to check the usage of dashboard.meta.dashboardNotFound:

✅ Verification successful

Let me gather more context about the FNDashboard variable and its usage.


Let me search for more context about the FNDashboard variable declaration and its usage.


Let me search for the FNDashboard type definition and its usage in the app/fn-app/types file.


Let me check the FNDashboard type definition in the types file.


Based on the gathered context, I can now provide a final response about the dashboard not found condition.

The condition dashboard.meta.dashboardNotFound && !FNDashboard is correctly implemented

The condition appropriately handles the "not found" scenario by:

  1. Checking if the dashboard is not found via dashboard.meta.dashboardNotFound
  2. Only showing the "not found" page when it's not a Function Dashboard (!FNDashboard)

This is correct because:

  • For regular dashboards, it shows the EntityNotFound component when a dashboard isn't found
  • For Function Dashboards (FNDashboard), which have special handling and different lifecycle, it skips showing the "not found" error as these are managed differently through the FNDashboard props interface
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of dashboard.meta.dashboardNotFound

rg "dashboard\.meta\.dashboardNotFound" public/app/

Length of output: 279


Script:

#!/bin/bash
# Search for FNDashboard declarations and usages
ast-grep --pattern 'const FNDashboard = $_'

# Also search for any other references to FNDashboard
rg "FNDashboard" -A 2 public/app/

Length of output: 132252


Script:

#!/bin/bash
# Search for FNDashboard in the specific file and its imports
rg -A 5 "FNDashboard" public/app/features/dashboard/containers/DashboardPage.tsx

Length of output: 5377


Script:

#!/bin/bash
# Search for FNDashboard type definition and related imports
rg -g "*.ts" -g "*.tsx" "FNDashboardProps" public/app/

Length of output: 2917


Script:

#!/bin/bash
# Get the content of the types file to understand FNDashboard interface
cat public/app/fn-app/types.ts

Length of output: 1025

@@ -58,7 +58,7 @@ export class DashboardPanelUnconnected extends PureComponent<Props> {
}
}

onInstanceStateChange = (value: any) => {
onInstanceStateChange = (value: unknown) => {
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Type change from any to unknown needs additional updates

The type change in DashboardPanel.tsx is part of a chain of components that handle panel state, but there are inconsistencies in the type definitions across the codebase:

  • packages/grafana-ui/src/components/PanelChrome/PanelContext.ts still uses any type
  • public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx already uses unknown
  • Several panel implementations (geomap, canvas, debug) are using this callback but rely on the context definition with any

To maintain type safety consistently, the type in PanelContext.ts should also be updated from any to unknown.

🔗 Analysis chain

Good improvement in type safety!

The change from any to unknown is a positive step towards better type safety. This enforces explicit type checking before using the value, reducing potential runtime errors.

Let's verify the impact on consuming components:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find components that pass values to onInstanceStateChange
# to ensure they handle the unknown type correctly

# Search for direct prop passing
rg -A 3 "onInstanceStateChange={" 

# Search for component implementations that might be affected
ast-grep --pattern 'onInstanceStateChange($value) {
  $$$
}'

Length of output: 208


Script:

#!/bin/bash
# Search for direct prop passing with escaped curly brace
rg -A 3 "onInstanceStateChange=\{" 

# Search for usage of onInstanceStateChange using ast-grep with proper syntax
ast-grep --pattern 'onInstanceStateChange = ($value) => {
  $$$
}'

# Search for any references to onInstanceStateChange
rg "onInstanceStateChange"

Length of output: 2433

Comment on lines +96 to +102
wrapper: css({
label: 'page-wrapper',
display: 'flex',
flex: '1 1 0',
flexDirection: 'column',
position: 'relative',
}),
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Based on the evidence gathered, I can now provide a final response:

Removal of bodyScrolling toggle requires careful consideration

The bodyScrolling feature toggle is extensively used across multiple components in the codebase for controlling scroll behavior and layout calculations. Removing it from the Page component's wrapper styles could lead to inconsistencies since:

  • It's actively used in AppChrome, NativeScrollbar, and other core components
  • It affects global styling through GlobalStyles/page.ts and elements.ts
  • It's integrated with layout calculations in multiple UI components

Please:

  • Keep the feature toggle condition in the wrapper styles to maintain consistency
  • If the goal is to remove this toggle, it should be part of a broader refactoring effort across all affected components
🔗 Analysis chain

Verify removal of bodyScrolling feature toggle

The style implementation has been simplified by removing the conditional logic based on config.featureToggles.bodyScrolling. Please confirm if this standardization of scrolling behavior is intentional and doesn't impact any existing functionality.


The search results show that bodyScrolling feature toggle is used extensively throughout the codebase, affecting scrolling behavior, layout calculations, and styling in multiple components. Let's verify if there are any recent changes or migrations related to this feature toggle.


Let's check if there are any recent changes specifically related to the bodyScrolling feature toggle in the Page component or related files.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other usages of bodyScrolling feature toggle
rg "featureToggles.bodyScrolling" -A 3

Length of output: 7304


Script:

#!/bin/bash
# Check recent commits mentioning bodyScrolling
git log --oneline -n 10 | grep -i "bodyscroll"

# Check if there are any migration or cleanup files related to this
fd -e ts -e tsx -e md | rg -i "migration|cleanup|scroll" 

Length of output: 4827


Script:

#!/bin/bash
# Check the git history of the Page component and related files
git log -p -- public/app/core/components/Page/Page.tsx | grep -A 5 "bodyScrolling"

# Check for any feature toggle documentation
fd -e md | xargs rg -i "bodyscrolling"

Length of output: 38

@@ -21,6 +21,7 @@ export interface LocationService {
getHistory: () => H.History;
getSearch: () => URLSearchParams;
getSearchObject: () => UrlQueryMap;
fnPathnameChange: (path: string, queryParams: any) => void;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Method signature needs improvements

The new method signature has several issues that should be addressed:

  1. Use a more specific type than any for queryParams (e.g., UrlQueryMap which is already imported)
  2. Consider following the existing method naming patterns (e.g., updatePathname)
  3. Add a return type annotation (likely void)
  4. Add JSDoc documentation like other interface methods
-  fnPathnameChange: (path: string, queryParams: any) => void;
+  /**
+   * Updates the current pathname and query parameters
+   * @param path - The new pathname
+   * @param queryParams - The new query parameters
+   */
+  updatePathname: (path: string, queryParams: UrlQueryMap) => void;
📝 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
fnPathnameChange: (path: string, queryParams: any) => void;
/**
* Updates the current pathname and query parameters
* @param path - The new pathname
* @param queryParams - The new query parameters
*/
updatePathname: (path: string, queryParams: UrlQueryMap) => void;

Comment on lines 64 to 73
<Router history={locationService.getHistory()}>
<LocationServiceProvider service={locationService}>
<CompatRouter>
<ModalsContextProvider>
<GlobalStyles />
{children}
</ModalsContextProvider>
</CompatRouter>
</LocationServiceProvider>
</Router>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Review the usage of nested routers to avoid potential conflicts

The component hierarchy includes both <BrowserRouter> and a nested <Router> using locationService.getHistory(). Ensure that nesting multiple routers does not introduce unexpected behavior or routing conflicts. If not necessary, consider simplifying the routing structure.

Comment on lines +146 to +150
panelPos.x = (count % 2) * GRID_COLUMN_COUNT;
panelPos.y = Math.floor(count / 2);
layout.push(panelPos);
count++;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix grid positioning error when applying panel filters

In the buildLayout method, the panelPos.x is set to either 0 or GRID_COLUMN_COUNT, which may exceed the grid boundaries since x should be between 0 and GRID_COLUMN_COUNT - 1. This could cause panels to render outside the visible grid area.

Adjust the positioning logic to divide the grid into two columns within the grid boundaries:

if (panelFilter.test(panel.title)) {
  panelPos.isResizable = false;
  panelPos.isDraggable = false;
-  panelPos.x = (count % 2) * GRID_COLUMN_COUNT;
+  const columnWidth = Math.floor(GRID_COLUMN_COUNT / 2);
+  panelPos.w = columnWidth;
+  panelPos.x = (count % 2) * columnWidth;
  panelPos.y = Math.floor(count / 2);
  layout.push(panelPos);
  count++;
}

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

Comment on lines +295 to +313
private resizeObserver?: ResizeObserver;
private rootEl: HTMLDivElement | null = null;
onMeasureRef = (rootEl: HTMLDivElement | null) => {
if (!rootEl) {
if (this.rootEl && this.resizeObserver) {
this.resizeObserver.unobserve(this.rootEl);
}
return;
}

this.rootEl = rootEl;
this.resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
this.setState({ width: entry.contentRect.width });
});
});

this.resizeObserver.observe(rootEl);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Properly disconnect ResizeObserver to prevent memory leaks

The ResizeObserver created in onMeasureRef is not disconnected when the component unmounts, which could lead to memory leaks. Ensure to disconnect the observer in componentWillUnmount.

Apply this diff to properly clean up the ResizeObserver:

componentWillUnmount() {
  this.eventSubs.unsubscribe();
+ if (this.resizeObserver && this.rootEl) {
+   this.resizeObserver.unobserve(this.rootEl);
+   this.resizeObserver = undefined;
+   this.rootEl = null;
+ }
}

onMeasureRef = (rootEl: HTMLDivElement | null) => {
  if (!rootEl) {
    if (this.rootEl && this.resizeObserver) {
      this.resizeObserver.unobserve(this.rootEl);
+     this.resizeObserver = undefined;
+     this.rootEl = null;
    }
    return;
  }
  // ... rest of the code ...
}
📝 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
private resizeObserver?: ResizeObserver;
private rootEl: HTMLDivElement | null = null;
onMeasureRef = (rootEl: HTMLDivElement | null) => {
if (!rootEl) {
if (this.rootEl && this.resizeObserver) {
this.resizeObserver.unobserve(this.rootEl);
}
return;
}
this.rootEl = rootEl;
this.resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
this.setState({ width: entry.contentRect.width });
});
});
this.resizeObserver.observe(rootEl);
};
private resizeObserver?: ResizeObserver;
private rootEl: HTMLDivElement | null = null;
onMeasureRef = (rootEl: HTMLDivElement | null) => {
if (!rootEl) {
if (this.rootEl && this.resizeObserver) {
this.resizeObserver.unobserve(this.rootEl);
this.resizeObserver = undefined;
this.rootEl = null;
}
return;
}
this.rootEl = rootEl;
this.resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
this.setState({ width: entry.contentRect.width });
});
});
this.resizeObserver.observe(rootEl);
};
componentWillUnmount() {
this.eventSubs.unsubscribe();
if (this.resizeObserver && this.rootEl) {
this.resizeObserver.unobserve(this.rootEl);
this.resizeObserver = undefined;
this.rootEl = null;
}
}

Comment on lines 163 to 165
if (!FNDashboard || isCustomDashboard) {
this.forceRouteReloadCounter = (this.props.history.location?.state as any)?.routeReloadCounter || 0;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid using any type assertions

Using (this.props.history.location?.state as any) bypasses TypeScript's type checking. Consider defining a proper type for location.state to enhance type safety.

Define an interface for the location state and update the code accordingly.

interface DashboardLocationState {
  routeReloadCounter?: number;
}

Then update the code:

-this.forceRouteReloadCounter = (this.props.history.location?.state as any)?.routeReloadCounter || 0;
+const locationState = this.props.history.location?.state as DashboardLocationState;
+this.forceRouteReloadCounter = locationState?.routeReloadCounter || 0;

Comment on lines +363 to 365
setScrollRef = (scrollElement: ScrollRefElement): void => {
this.setState({ scrollElement });
};
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 null check for scrollElement in setScrollRef

Ensure that scrollElement is not null before updating the state to prevent potential runtime errors.

Apply this diff to add a null check:

 setScrollRef = (scrollElement: ScrollRefElement): void => {
+  if (scrollElement) {
     this.setState({ scrollElement });
+  }
 };
📝 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
setScrollRef = (scrollElement: ScrollRefElement): void => {
this.setState({ scrollElement });
};
setScrollRef = (scrollElement: ScrollRefElement): void => {
if (scrollElement) {
this.setState({ scrollElement });
}
};

Comment on lines 402 to 409
console.log('Edit Panel: ', { editPanel, sectionNav, pageNav, isFNDashboardEditable });
console.log('Dashboard settings: ', { editView: queryParams.editview, pageNav, sectionNav, isFNDashboardEditable });
console.log('Add Widget: ', {
isFNDashboardEditable,
addWidget: queryParams.addWidget,
configToggle: config.featureToggles.vizAndWidgetSplit,
});

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove console.log statements before production

Debugging statements using console.log should be removed to prevent unwanted logs in production.

Apply this diff to remove the console logs:

-    console.log('Edit Panel: ', { editPanel, sectionNav, pageNav, isFNDashboardEditable });
-    console.log('Dashboard settings: ', { editView: queryParams.editview, pageNav, sectionNav, isFNDashboardEditable });
-    console.log('Add Widget: ', {
-      isFNDashboardEditable,
-      addWidget: queryParams.addWidget,
-      configToggle: config.featureToggles.vizAndWidgetSplit,
-    });

</>
)}
{FNTimeRange}
</div>
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 null check when accessing DOM elements

Using document.getElementById(controlsContainer)! assumes the element exists. Add a null check to prevent potential runtime errors.

Apply this diff to include a null check:

-<Portal container={document.getElementById(controlsContainer)!}>
+const containerElement = document.getElementById(controlsContainer);
+if (containerElement) {
+  <Portal container={containerElement}>

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

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.

1 participant