Skip to content

Comments

Introduce devtools panel for debugging and testing purposes#6241

Open
mirekm wants to merge 96 commits intomainfrom
feature/devtools
Open

Introduce devtools panel for debugging and testing purposes#6241
mirekm wants to merge 96 commits intomainfrom
feature/devtools

Conversation

@mirekm
Copy link
Member

@mirekm mirekm commented Dec 26, 2025

A developer tools panel that allows registering debug tools from anywhere in the app.

For now it includes a notifications debug panel for testing toast messages (trigger different notification types, test auto-dismiss behavior, simulate long descriptions, etc.). Useful for development and QA.

image

mirekm added 30 commits December 5, 2025 18:38
When no products are available in the channel or channel is inactive.
- Create new OrderCaptureDialog component using macaw-ui-next
- Supports three capture options: order total, authorized amount, custom
- Shows contextual warnings when authorized differs from total
- Integrate with both Legacy Payments API and Transactions API flows
- Include development overrides for testing (to be removed)
@mirekm mirekm requested a review from a team as a code owner December 26, 2025 13:12
@changeset-bot
Copy link

changeset-bot bot commented Dec 26, 2025

🦋 Changeset detected

Latest commit: 9f2d8e5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
saleor-dashboard Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a developer tools panel for debugging and testing, along with significant refactoring of the notification system and order-related components. The key changes include:

  • DevTools Panel: A new keyboard-activated (Cmd+Shift+D) debug panel system with pluggable tools
  • Notification System Migration: Replaces custom message system with Sonner toast library
  • Order Capture Dialog: Unified, comprehensive payment capture dialog supporting both legacy and transaction APIs
  • Order Summary Improvements: Enhanced order summary with editable shipping/discount fields for draft/unconfirmed orders

Reviewed changes

Copilot reviewed 78 out of 79 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/components/DevTools/* New devtools panel infrastructure with registration system
src/components/notifications/* New Sonner-based notification system with Toast component
src/components/messages/* Deleted old message system files
src/hooks/useNotifier/* Refactored to use new notification system
src/orders/components/OrderCaptureDialog/* New unified capture dialog with comprehensive validation
src/orders/components/OrderSummary/* Enhanced with editable shipping/discount functionality
src/orders/components/OrderDraftDetails/* Refactored to use new OrderSummary component
src/orders/views/OrderDetails/* Updated to use OrderCaptureDialog instead of OrderPaymentDialog
src/index.tsx Integrated DevTools and new notification providers
package.json Added Sonner dependency (^2.0.7)
.github/actions/prepare-api-variables/action.yml Improved branch detection logic
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions
Copy link
Contributor

Differences Found

⚠️ 1 packages or licenses were added.

Expand
License	Package
MIT	sonner

Summary

Expand
License Name Package Count Packages
0BSD 1
Packages
  • tslib
CC0-1.0 1
Packages
  • type-fest
MIT/X11 1
Packages
  • nub
MPL-1.1 1
Packages
  • harmony-reflect
MPL-2.0 1
Packages
  • dompurify
Public Domain 1
Packages
  • jsonify
Python-2.0 1
Packages
  • argparse
WTFPL 1
Packages
  • utf8-byte-length
<<missing>> 2
Packages
  • busboy
  • streamsearch
CC-BY-4.0 2
Packages
  • @saleor/macaw-ui
  • caniuse-lite
SEE LICENSE IN LICENSE 2
Packages
  • posthog-js
  • spawndamnit
BlueOak-1.0.0 4
Packages
  • glob
  • lru-cache
  • minimatch
  • path-scurry
BSD-2-Clause 23
Packages
  • browser-process-hrtime
  • css-select
  • css-what
  • domelementtype
  • domhandler
  • domutils
  • dotenv
  • dotenv-expand
  • entities
  • escodegen
  • eslint-scope
  • espree
  • esprima
  • esrecurse
  • estraverse
  • esutils
  • nth-check
  • regjsparser
  • stringify-object
  • terser
  • And 3 more...
BSD-3-Clause 42
Packages
  • @saleor/app-sdk
  • @sentry/cli
  • @sentry/cli-darwin
  • @sentry/cli-linux-arm
  • @sentry/cli-linux-arm64
  • @sentry/cli-linux-i686
  • @sentry/cli-linux-x64
  • @sentry/cli-win32-arm64
  • @sentry/cli-win32-i686
  • @sentry/cli-win32-x64
  • @sinonjs/commons
  • @sinonjs/fake-timers
  • abab
  • asn1js
  • babel-plugin-istanbul
  • chroma-js
  • dataloader
  • diff
  • esquery
  • exenv
  • And 22 more...
ISC 47
Packages
  • @istanbuljs/load-nyc-config
  • anymatch
  • boolbase
  • cli-width
  • cliui
  • electron-to-chromium
  • fastq
  • flatted
  • fs.realpath
  • get-caller-file
  • get-own-enumerable-property-symbols
  • glob
  • glob-parent
  • graceful-fs
  • inflight
  • inherits
  • ini
  • isexe
  • knip
  • lru-cache
  • And 27 more...
Apache-2.0 54
Packages
  • @editorjs/editorjs
  • @eslint/config-array
  • @eslint/config-helpers
  • @eslint/core
  • @eslint/object-schema
  • @eslint/plugin-kit
  • @humanfs/core
  • @humanfs/node
  • @humanwhocodes/module-importer
  • @humanwhocodes/retry
  • @opentelemetry/api
  • @opentelemetry/semantic-conventions
  • @playwright/test
  • @pollyjs/adapter
  • @pollyjs/core
  • @pollyjs/persister
  • @pollyjs/utils
  • @swc/core
  • @swc/core-darwin-arm64
  • @swc/core-darwin-x64
  • And 34 more...
MIT 1295
Packages
  • @adobe/css-tools
  • @apollo/client
  • @ardatan/relay-compiler
  • @ardatan/sync-fetch
  • @babel/code-frame
  • @babel/compat-data
  • @babel/core
  • @babel/generator
  • @babel/helper-annotate-as-pure
  • @babel/helper-compilation-targets
  • @babel/helper-create-class-features-plugin
  • @babel/helper-globals
  • @babel/helper-member-expression-to-functions
  • @babel/helper-module-imports
  • @babel/helper-module-transforms
  • @babel/helper-optimise-call-expression
  • @babel/helper-plugin-utils
  • @babel/helper-replace-supers
  • @babel/helper-skip-transparent-expression-wrappers
  • @babel/helper-string-parser
  • And 1275 more...

Copilot AI review requested due to automatic review settings December 26, 2025 19:18
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 57 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +22 to +23
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [panel.id]);
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

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

The useEffect dependency array includes only panel.id, but the entire panel object (including label and component) could change between renders. If panel.label or panel.component changes while panel.id stays the same, the registered panel won't update. Either include the full panel object in dependencies (and ensure proper memoization in calling components), or use panel.id, panel.label, and panel.component separately in the dependency array.

Suggested change
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [panel.id]);
}, [register, panel.id, panel.label, panel.component]);

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +137
const [selectedOption, setSelectedOption] = useState<CaptureAmountOption>(getDefaultOption);
const [customAmount, setCustomAmount] = useState<string>(getDefaultCustomAmount);
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

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

The useState initializer is calling functions that depend on props, but these functions are defined inside the component body. This means the initial state will only be computed once on mount with the initial prop values. If the dialog is reused with different props (without unmounting), the initial state won't reflect the new props. While there's a useEffect that resets the state when 'open' changes, it would be cleaner and more reliable to use a callback function in useState that captures the current prop values at initialization time, or to use the functions directly without calling them.

Copilot uses AI. Check for mistakes.
Comment on lines +140 to +146
useEffect(() => {
if (open) {
setSelectedOption(getDefaultOption());
setCustomAmount(getDefaultCustomAmount());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

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

The useEffect has disabled the exhaustive-deps rule, but the functions getDefaultOption and getDefaultCustomAmount depend on several props and state values (authStatus, availableToCapture, remainingToPay). If any of these values change while the dialog is open, the state won't update accordingly. Consider either including all dependencies properly, or restructuring the code to avoid the need for disabling the rule. Alternatively, if the reset should truly only happen when 'open' changes, the functions should be memoized with their dependencies.

Copilot uses AI. Check for mistakes.

const context = useMemo<INotificationContext>(
() => ({
show: () => {},
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

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

The 'show' method in the context is implemented as an empty function that does nothing. This means the NotificationContext.show() will not work as expected - it should actually trigger a notification. Based on the codebase, it appears notifications are now handled directly through useNotifier hook which uses toast.custom from Sonner, so this context method is likely vestigial. Consider either implementing it properly or documenting that notifications should be triggered via useNotifier hook instead of the context directly.

Suggested change
show: () => {},
show: () => {
// Deprecated: NotificationContext.show is vestigial.
// Use the `useNotifier` hook to trigger notifications instead.
console.warn(
"NotificationContext.show is deprecated and does not trigger notifications. " +
"Please use the `useNotifier` hook instead.",
);
},

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Dec 26, 2025

Codecov Report

❌ Patch coverage is 70.72600% with 125 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.18%. Comparing base (2b2311e) to head (9f2d8e5).

Files with missing lines Patch % Lines
src/components/DevTools/DevToolsPanel.tsx 16.66% 25 Missing ⚠️
...ews/OrderDetails/OrderUnconfirmedDetails/index.tsx 0.00% 21 Missing ⚠️
...rs/views/OrderDetails/OrderNormalDetails/index.tsx 0.00% 18 Missing ⚠️
src/components/notifications/Toast.tsx 74.62% 14 Missing and 3 partials ⚠️
...mponents/notifications/NotificationsDebugPanel.tsx 26.31% 14 Missing ⚠️
...mponents/OrderCaptureDialog/OrderCaptureDialog.tsx 93.93% 5 Missing and 3 partials ⚠️
src/components/DevTools/DevToolsProvider.tsx 82.75% 5 Missing ⚠️
.../components/notifications/NotificationProvider.tsx 66.66% 5 Missing ⚠️
src/orders/views/OrderDetails/OrderDetails.tsx 0.00% 3 Missing ⚠️
src/giftCards/GiftCardCreateDialog/utils.ts 0.00% 2 Missing ⚠️
... and 6 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6241      +/-   ##
==========================================
+ Coverage   40.96%   41.18%   +0.21%     
==========================================
  Files        2432     2434       +2     
  Lines       40957    41246     +289     
  Branches     9414     9540     +126     
==========================================
+ Hits        16779    16986     +207     
- Misses      22939    23015      +76     
- Partials     1239     1245       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@witoszekdev witoszekdev left a comment

Choose a reason for hiding this comment

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

it looks to me like there are 3 PRs in one here 😅

we have:

  • redesign of notifications
  • new order capture dialog
  • devtools

To keep this simple let's focus on notifications and capture dialog and if we device to keep devtools instead of using Storybook we can create separate PR for that.

There are issues with the code that will create bugs + some of the logic for currency input is duplicated to what we already have in Dashboard

"saleor-dashboard": patch
---

Introduce new, unified order capture dialog
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: let's add more description here what's new about this capture dialog

Suggested change
Introduce new, unified order capture dialog
Introduced new, unified order capture dialog

id: "UUVUyy",
},
});
// Re-export common messages for backward compatibility
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: let's add a todo to remove this altogether

Suggested change
// Re-export common messages for backward compatibility
// Re-export common messages for backward compatibility
// TODO: Remove and use `commonMessages` from @dashboard/intl

data: any;
intl: IntlShape;
notify: (message: IMessage) => void;
notify: (notification: INotification) => void;
Copy link
Member

Choose a reason for hiding this comment

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

question: Shouldn't we use INotificationCallback here? isn't it the same thing?

Suggested change
notify: (notification: INotification) => void;
notify: INotificationCallback;

Copy link
Member

Choose a reason for hiding this comment

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

question: Do we really need this? I think we can test notifications inside Storybook instead 😄

This will be shipped in our production code and it feels like adding a Conami code, rather than proper devtools 😅

type Status = "success" | "error" | "info" | "warning";

export interface INotification {
actionBtn?: {
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: imho it's better to use verbose names whenever possible

Suggested change
actionBtn?: {
actionButton?: {

Comment on lines +92 to +108
// Determine authorization status
const getAuthorizationStatus = (): AuthorizationStatus => {
// Check if fully charged first (nothing left to pay)
if (remainingToPay <= 0) {
return "charged";
}

if (availableToCapture <= 0) {
return "none";
}

if (availableToCapture >= remainingToPay) {
return "full";
}

return "partial";
};
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: instead of creating a function which gets it's own closure from our state anyway we should re-write this to a hook

setSelectedOption(getDefaultOption());
setCustomAmount(getDefaultCustomAmount());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Copy link
Member

Choose a reason for hiding this comment

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

issue: this warning is important here, these functions have stale closure when we call them in useEffect which means they will reset state to invalid values, instead of using useEffect we can rely on React to do cleanup for us, if we render this component conditionally wherever is used:

{params.action === "transaction-charge-action" && (
  <OrderCaptureDialog ... />
)}

and we can remove open as prop.

}
orderBalance={order?.totalBalance ?? { amount: 0, currency: "USD" }}
isTransaction
open={true}
Copy link
Member

Choose a reason for hiding this comment

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

we should remove open since we're already conditionally rendering this component

errors={orderPaymentCapture.opts.data?.orderCapture?.errors ?? []}
orderTotal={order?.total.gross ?? { amount: 0, currency: "USD" }}
authorizedAmount={order?.totalAuthorized ?? { amount: 0, currency: "USD" }}
open={params.action === "capture"}
Copy link
Member

Choose a reason for hiding this comment

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

issue: we should replace this with conditionally rendering component, and remove open parameter

@witoszekdev
Copy link
Member

ok, I see that these were indeed 3 separate PRs 🙈

@mirekm please make sure you look at the commits, if they were created from the same branch we will have all unmerged changes in the diff, and later we'll have conflicts.

This can be fixed by:

git rebase --onto main previous-branch feature/devtools

where previous-branch is the name of the branch from those 2 other PRs 😄

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.

4 participants