• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / f7bc8e83-3560-4369-be54-50d9795958aa

06 Oct 2025 01:05PM UTC coverage: 88.879% (+0.02%) from 88.863%
f7bc8e83-3560-4369-be54-50d9795958aa

Pull #5924

circleci

web-flow
Merge pull request #5941 from mozilla/MPP-4348-propagate-utm-params-hydration

Fix hydration errors
Pull Request #5924: MPP-4348: propagate UTM params from landing page to SubPlat and Accounts URLs

2925 of 3933 branches covered (74.37%)

Branch coverage included in aggregate %.

37 of 43 new or added lines in 12 files covered. (86.05%)

1 existing line in 1 file now uncovered.

18110 of 19734 relevant lines covered (91.77%)

13.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/frontend/src/pages/flags.page.tsx
1
import { FormEventHandler, useState } from "react";
×
2
import { NextPage } from "next";
3
import { SWRResponse } from "swr";
4
import styles from "./flags.module.scss";
×
5
import { Layout } from "../components/layout/Layout";
×
6
import { Button } from "../components/Button";
×
7
import { getRuntimeConfig } from "../config";
×
8
import { useRuntimeData } from "../hooks/api/runtimeData";
×
9
import { isFlagActive } from "../functions/waffle";
×
10
import { apiFetch, useApiV1 } from "../hooks/api/api";
×
11
import { BlockIcon, CheckIcon } from "../components/Icons";
×
12
import { toast } from "react-toastify";
×
NEW
13
import { useUtmApplier } from "../hooks/utmApplier";
×
14

15
const Flags: NextPage = () => {
×
16
  const runtimeData = useRuntimeData();
×
17
  const flagData = useFlagData();
×
18
  const [actionInput, setActionInput] = useState("");
×
19
  const [flagInput, setFlagInput] = useState("");
×
NEW
20
  const applyUtmParams = useUtmApplier();
×
21

22
  if (!runtimeData.data) {
×
23
    return null;
×
24
  }
25

26
  if (!isFlagActive(runtimeData.data, "manage_flags") || flagData.error) {
×
NEW
27
    document.location.assign(applyUtmParams(getRuntimeConfig().fxaLoginUrl));
×
28
    return null;
×
29
  }
30

31
  const flags =
32
    flagData.data?.filter((flag) => flag.name !== "manage_flags") ?? [];
×
33

34
  async function createOrUpdate(flagName: string, enable: boolean) {
35
    const existingFlag = flagData.data?.find((flag) => flag.name === flagName);
×
36

37
    if (existingFlag) {
×
38
      return apiFetch(`/flags/${existingFlag.id}/`, {
×
39
        method: "PATCH",
40
        body: JSON.stringify({
41
          everyone: enable,
42
        }),
43
      });
44
    }
45

46
    return await apiFetch("/flags/", {
×
47
      method: "POST",
48
      body: JSON.stringify({
49
        name: flagName,
50
        everyone: enable,
51
      }),
52
    });
53
  }
54

55
  const onSubmit: FormEventHandler = async (event) => {
×
56
    event.preventDefault();
×
57

58
    if (!["enable", "disable"].includes(actionInput)) {
×
59
      return;
×
60
    }
61

62
    const enable = actionInput === "enable";
×
63
    const existingFlag = flags.find((flag) => flag.name === flagInput);
×
64

65
    if (existingFlag?.everyone === null) {
×
66
      toast(
×
67
        <>
68
          The flag <output>{flagInput}</output> already has a non-global value,
69
          and therefore cannot be {enable ? "enabled" : "disabled"} for
×
70
          everyone.
71
        </>,
72
        { type: "error" },
73
      );
74
      return;
×
75
    }
76

77
    const response = await createOrUpdate(flagInput, enable);
×
78
    flagData.mutate();
×
79
    if (response.ok) {
×
80
      toast(
×
81
        <>
82
          Flag <output>{flagInput}</output> {enable ? "enabled" : "disabled"}{" "}
×
83
          successfully
84
        </>,
85
        { type: "success" },
86
      );
87
      // Don't make it too easy to update multiple flags in a row
88
      // (to avoid making mistakes):
89
      setActionInput("");
×
90
      setFlagInput("");
×
91
    } else {
92
      toast(
×
93
        <>
94
          Something went wrong {enable ? "enabling" : "disabling"} flag{" "}
×
95
          <output>{flagInput}</output>
96
        </>,
97
        { type: "error" },
98
      );
99
    }
100
    return;
×
101
  };
102

103
  return (
104
    <Layout runtimeData={runtimeData.data}>
105
      <main className={styles.wrapper}>
106
        <table className={styles["flag-list"]}>
107
          <thead>
108
            <tr>
109
              <th>Active?</th>
110
              <th>Flag</th>
111
            </tr>
112
          </thead>
113
          <tbody>
114
            {flags.map((flag) => (
115
              <tr
×
116
                key={flag.name}
117
                className={
118
                  flag.everyone === null
×
119
                    ? styles["is-non-global"]
120
                    : flag.everyone
×
121
                      ? styles["is-active"]
122
                      : styles["is-inactive"]
123
                }
124
              >
125
                <td>
126
                  {flag.everyone ? (
127
                    <CheckIcon alt="Active" />
×
128
                  ) : (
129
                    <BlockIcon alt="Inactive" />
130
                  )}
131
                </td>
132
                <td>{flag.name}</td>
133
              </tr>
134
            ))}
135
          </tbody>
136
        </table>
137
        <form onSubmit={onSubmit} className={styles["flag-form"]}>
138
          <p>
139
            To avoid accidentally enabling/disabling the wrong flags, you have
140
            to type out explicitly what action to take.
141
          </p>
142
          <div className={styles.field}>
143
            <label htmlFor="action">Which flag do you want to modify?</label>
144
            <input
145
              type="text"
146
              id="action"
147
              autoComplete="off"
148
              pattern="(?!manage_flags)(.*)"
149
              required={true}
150
              placeholder={`e.g. \`${flags[0]?.name}\``}
151
              value={flagInput}
152
              onChange={(e) => setFlagInput(e.target.value)}
×
153
            />
154
          </div>
155
          <div className={styles.field}>
156
            <label htmlFor="action">
157
              Do you want to <output>enable</output> or <output>disable</output>{" "}
158
              it?
159
            </label>
160
            <input
161
              type="text"
162
              id="action"
163
              autoComplete="off"
164
              pattern="enable|disable"
165
              required={true}
166
              placeholder="`enable` or `disable`"
167
              value={actionInput}
168
              onChange={(e) => setActionInput(e.target.value)}
×
169
            />
170
          </div>
171
          <Button type="submit">Set flag status</Button>
172
        </form>
173
      </main>
174
    </Layout>
175
  );
176
};
177

178
type Flag = { id: number; name: string; everyone: boolean; note: string };
179
function useFlagData() {
180
  const flagData: SWRResponse<Flag[], unknown> = useApiV1("/flags/");
×
181
  return flagData;
×
182
}
183

184
export default Flags;
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc