Skip to content

Conversation

@codemist
Copy link
Collaborator

References:

Jira: MNTOR-5066
Figma:

Description

This PR begins the process of removing all data-broker–related information from the front end while minimizing changes to the backend as much as possible. I started with the Dashboard and am working outward from there, since everything is deeply connected and any component referencing OneRepScanResultRow or related OneRep types needs to be carefully unraveled.

I could be wrong here (and also because we didn't really formally decide on a protocol for the FE removal) but because of the amount of prop drilling and cross-component coupling, doing this in tiny incremental PRs each removal step tends to cascade into linting issues, build errors, or broken types. Instead, this PR represents the first major chunk in a slow, deliberate phase-out. It should be reviewed in sections, and it will definitely require a thorough regression test.

The overall goal is to steadily strip out all OneRep/data-broker UI surfaces without destabilizing unrelated areas of the app. Subsequent PRs will continue this clean-up process once this foundation is in place.

Screenshot (if applicable)

Not applicable.

How to test

Checklist (Definition of Done)

  • Localization strings (if needed) have been added.
  • Commits in this PR are minimal and have descriptive commit messages.
  • I've added or updated the relevant sections in readme and/or code comments
  • I've added a unit test to test for potential regressions of this bug.
  • If this PR implements a feature flag or experimentation, I've checked that it still works with the flag both on, and with the flag off.
  • If this PR implements a feature flag or experimentation, the Ship Behind Feature Flag status in Jira has been set
  • Product Owner accepted the User Story (demo of functionality completed) or waived the privilege.
  • All acceptance criteria are met.
  • Jira ticket has been updated (if needed) to match changes made during the development process.
  • Jira ticket has been updated (if needed) with suggestions for QA when this PR is deployed to stage.

@codemist codemist requested a review from Vinnl November 26, 2025 19:04
@Vinnl Vinnl removed their request for review November 27, 2025 15:15
@Vinnl
Copy link
Collaborator

Vinnl commented Nov 27, 2025

As just discussed: I've unassigned myself as a reviewer for now, as you can focus on removing everything that's related (backend as well), and then re-assign me when the checks succeed and it's ready for review again.

Copy link
Collaborator

Choose a reason for hiding this comment

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

FYI these cron jobs are handled in #6289, which is scheduled to be merged after Dec 17

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the heads up, just approved. Please merge it when you can; I’ll rebase right after to avoid more merge conflicts.
Note: since this is part of the deprecation work without a feature flag, we’ll probably want to hold off on any production pushes this week (and next).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I was planning on waiting to merge until closer to the release date (we can't release before Dec 17). If it's in there, it will make releasing anything else much more complex because we'll have to cherry-pick commits... Thoughts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@kschelonka Would it make sense to base that PR on this branch, so we can test/release in one go?

@codemist codemist added the 🛑 Do Not Merge Do not merge this PR, even if approved. label Dec 2, 2025
@codemist codemist marked this pull request as draft December 2, 2025 13:19
@codemist codemist marked this pull request as ready for review December 4, 2025 05:39
@codemist codemist requested a review from Vinnl December 4, 2025 05:40
@codemist
Copy link
Collaborator Author

codemist commented Dec 4, 2025

Build is finally passing. I’m working through the unit tests now, mostly cleanup around the calculation logic. I haven’t touched any of the Plus shutdown logic yet, so I kept the hasPremium checks as-is for now (only in that area) to avoid increasing the scope. It’s in decent enough shape for a review if you want to take a look, and I’ll keep chipping away at the remaining fixes.

props.experimentData ?? defaultExperimentData["Features"];
const enabledFeatureFlags = props.enabledFeatureFlags ?? [];
return (
<AccountsMetricsFlowProvider
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Traced all of this logic back to #4822 which is Plus related.

Comment on lines 85 to 111
it("sends telemetry when a free scan CTA is shown in the viewport", async () => {
const mockedRecord = useTelemetry();
const ComposedDashboard = composeStory(LandingNonUs, Meta);
render(<ComposedDashboard />);

// jsdom will complain about not being able to navigate to a different page
// after clicking the link; suppress that error, as it's not relevant to the test:
jest.spyOn(console, "error").mockImplementation(() => undefined);

// The useViewTelemetry ref is attached to the form, not the button
const submitButton = screen.getAllByRole("button", {
name: "Get free scan",
});
const form = submitButton[0].closest("form");
expect(form).toBeInTheDocument();

// Trigger intersection on the form element
await act(async () => {
mockIsIntersecting(form!, true);
});

expect(mockedRecord).toHaveBeenCalledWith(
"ctaButton",
"view",
expect.objectContaining({ button_id: "clicked_get_scan_header" }),
);
});
Copy link
Collaborator Author

@codemist codemist Dec 5, 2025

Choose a reason for hiding this comment

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

I traced this component back to https://github.com/mozilla/blurts-server/pull/4794/files, so I rewrote the test to only account for the “Get Free Scan” non-US state.

@@ -0,0 +1,66 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A lot of the old dashboard US/Plus tests added coverage in places that were hard to separate from the core interactions in the FixView component (which is basically the foundation for all guided resolution flows). It ended up being cleaner to just rewrite this part instead of trying to untangle the old logic.

isDismissable={true}
>
<Dialog
onDismiss={() => overlayTriggerState.close()}
Copy link
Collaborator Author

@codemist codemist Dec 5, 2025

Choose a reason for hiding this comment

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

I tracked down why onDismiss wasn’t being passed by default, it was only missing in this dialog component, which seemed inconsistent with our patterns. The dismiss button really should be a non-optional part of the dialog component, so I made it unconditional and added it directly to this component. It looked fine in Storybook, so I also added a test for coverage.

@codemist codemist removed the 🛑 Do Not Merge Do not merge this PR, even if approved. label Dec 5, 2025
@codemist
Copy link
Collaborator Author

codemist commented Dec 5, 2025

Update: I could definitely spend more time fully cleaning up every last piece of code related to sunsetting Plus, but this should be a solid starting point to meet the Dec 17 deadline. There are still a few scattered mentions of Plus/Premium/subscriptions across the codebase, and some additional decoupling of feature flags that we can do later. But overall, I think this is in a good enough state for review (coverage back at 100%).

I've tried to leave comments where the reasoning behind a change wasn’t immediately obvious, or where I removed something that might raise questions. I didn’t annotate every file because that would get unwieldy, so if anything feels unclear, please just ping me, I can spend some extra time doing a self-review and adding more context where needed.

The remaining follow-up steps I see:

  • Remove all OneRep types from the Knex tables file.
  • I’ve kept the hasPremium function for now to support the Plus shutdown banner, it should behave the same as before, and that’s the only place it's currently used.

There’s still some potential cleanup around naming conventions in Storybook components and unused utility functions, but that’s starting to veer into tech debt rather than sunset work, so I’m pausing on that for now.

Let me know if anything looks off or if you'd like me to walk through parts of it!

@codemist codemist requested a review from kschelonka December 5, 2025 19:41
const session = await getServerSession();
if (
!session?.user?.email ||
!isAdmin(session.user.email) ||
Copy link
Collaborator

Choose a reason for hiding this comment

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

question: this doesn't seem related to onerep specifically. can you explain the motivation behind the changes here?

lastScanDate={null}
// We're not going to run experiments on the feature flag page (it's
// not user-visible), so no need to fetch experiment data:
experimentData={defaultExperimentData["Features"]}
Copy link
Collaborator

Choose a reason for hiding this comment

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

question: the default data is no longer necessary?

return notFound();
}

const enabledFeatureFlags = await getEnabledFeatureFlags({
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll stop commenting after this one, but just curious why removing feature flag integrations

if (props.brokers && props.brokers !== "no-scan") {
const scanInProgress = props.brokers === "scan-in-progress";
scanData.scan = scanInProgress ? mockedScanInProgress : mockedScan;
const scanCount = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

unneeded const maybe?

parseIso8601Datetime((b as OnerepScanResultRow).created_at);
const arraySortedByDate = breachesDataArray.sort((a, b) => {
const dateA = a.addedDate;
const dateB = b.addedDate;
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: this can be onelined

const arraySortedByDate = breachesDataArray.sort((a, b) => b.addedDate.getTime() - a.addedDate.getTime())

Comment on lines -465 to -473
if (!hasUnresolvedExposures && hasFixedExposures) {
return (
<>
<Image src={AllFixedIllustration} alt="" />
<strong>
{l10n.getString("dashboard-exposures-all-fixed-label")}
</strong>
{freeScanCta}
</>
Copy link
Collaborator

Choose a reason for hiding this comment

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

this seems still relevant? if I'm reading it correctly it should also be shown for if you fixed all breaches? or is this broker-specific

export const FixView = (props: FixViewProps) => {
const l10n = useL10n();
const recordTelemetry = useTelemetry();
const isResolutionLayout = [
Copy link
Collaborator

Choose a reason for hiding this comment

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

why is this no longer relevant?

import { CONST_URL_MOZILLA_BASKET } from "../../../../../../../../constants";

// The monthly email for free users is currently disabled; see MNTOR-4970.
const isFreeMonthlyMonitorReportDisabledSeeMNTOR4970 = true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

😅

Comment on lines +29 to +36
const maxEmails = () => {
const emails = [];
for (let i = 1; i <= CONST_MAX_NUM_ADDRESSES; i++) {
emails.push(mockRandomEmail(i));
}

return emails;
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: there's a nice one-liner for this instead of mutating an array:

Array.from({ length: CONST_MAX_NUM_ADDRESSES }, (_, index) => mockRandomEmail(index));

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