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

mozilla / fx-private-relay / 3bc340e2-329f-4ed2-8700-adaaac8d78c8

15 Dec 2023 06:50PM CUT coverage: 73.514% (-0.1%) from 73.614%
3bc340e2-329f-4ed2-8700-adaaac8d78c8

push

circleci

jwhitlock
Use branch database with production tests

Previously, migrations tests were run with production code, branch
requirements, and branch migrations. Now they run with production
requirements, so that third-party migrations are tested as well.

This uses pytest --reuse-db to create a test database with the branch's
migrations, and then a pip install with the production code. This more
closely emulates the mixed environment during a deploy.

1962 of 2913 branches covered (0.0%)

Branch coverage included in aggregate %.

6273 of 8289 relevant lines covered (75.68%)

19.91 hits per line

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

73.17
/frontend/src/components/dashboard/aliases/Alias.tsx
1
import { useRef, useState, ReactNode } from "react";
2✔
2
import { useToggleState, useTooltipTriggerState } from "react-stately";
2✔
3
import {
4
  mergeProps,
5
  useToggleButton,
6
  useTooltip,
7
  useTooltipTrigger,
8
} from "react-aria";
2✔
9
import styles from "./Alias.module.scss";
2✔
10
import { ArrowDownIcon, CopyIcon, HideIcon } from "../../Icons";
2✔
11
import IllustrationHoliday from "../../../../public/illustrations/holiday.svg";
2✔
12
import IllustrationLibrary from "../../../../public/illustrations/library.svg";
2✔
13
import {
14
  AliasData,
15
  getFullAddress,
16
  isBlockingLevelOneTrackers,
17
} from "../../../hooks/api/aliases";
2✔
18
import { LabelEditor } from "./LabelEditor";
2✔
19
import { UserData } from "../../../hooks/api/user";
20
import { ProfileData } from "../../../hooks/api/profile";
21
import { renderDate } from "../../../functions/renderDate";
2✔
22
import { AliasDeletionButton } from "./AliasDeletionButton";
2✔
23
import { getRuntimeConfig } from "../../../config";
2✔
24
import { getLocale } from "../../../functions/getLocale";
2✔
25
import { BlockLevel, BlockLevelSlider } from "./BlockLevelSlider";
2✔
26
import { RuntimeData } from "../../../hooks/api/runtimeData";
27
import { isFlagActive } from "../../../functions/waffle";
2✔
28
import { isPeriodicalPremiumAvailableInCountry } from "../../../functions/getPlan";
2✔
29
import { useL10n } from "../../../hooks/l10n";
2✔
30
import { Localized } from "../../Localized";
2✔
31

32
// Deprecated, flag "mask_redesign" is in use and uses <MaskCard/> instead of <Alias/>
33

34
export type Props = {
35
  alias: AliasData;
36
  user: UserData;
37
  profile: ProfileData;
38
  onUpdate: (updatedFields: Partial<AliasData>) => void;
39
  onDelete: () => void;
40
  isOpen: boolean;
41
  onChangeOpen: (isOpen: boolean) => void;
42
  showLabelEditor?: boolean;
43
  runtimeData?: RuntimeData;
44
};
45

46
/**
47
 * A card to manage (toggle it on/off, view details, ...) a single alias.
48
 */
49
export const Alias = (props: Props) => {
146✔
50
  const l10n = useL10n();
141✔
51
  const [justCopied, setJustCopied] = useState(false);
141✔
52

53
  const expandButtonRef = useRef<HTMLButtonElement>(null);
140✔
54
  const expandButtonState = useToggleState({
140✔
55
    isSelected: props.isOpen === true,
56
    onChange: props.onChangeOpen,
57
  });
58
  const expandButtonProps = useToggleButton(
140✔
59
    {},
60
    expandButtonState,
61
    expandButtonRef,
62
  ).buttonProps;
63

64
  const address = getFullAddress(props.alias);
140✔
65

66
  const copyAddressToClipboard = () => {
140✔
67
    navigator.clipboard.writeText(address);
×
68
    setJustCopied(true);
×
69
    setTimeout(() => {
×
70
      setJustCopied(false);
×
71
    }, 1 * 1000);
72
  };
73

74
  const labelEditor = props.showLabelEditor ? (
140✔
75
    <div className={styles["label-editor-wrapper"]}>
76
      <LabelEditor
77
        label={props.alias.description}
78
        onSubmit={(newLabel) => props.onUpdate({ description: newLabel })}
3✔
79
      />
80
    </div>
81
  ) : null;
82

83
  let backgroundImage = undefined;
140✔
84
  if (
140!
85
    [
86
      "Holiday",
87
      "Holidays",
88
      "Vakantie",
89
      "Urlaub",
90
      "Ferien",
91
      "Vacances",
92
    ].includes(props.alias.description)
93
  ) {
94
    backgroundImage = IllustrationHoliday.src;
×
95
  }
96
  if (
140!
97
    [
98
      "Library",
99
      "Bibliotheek",
100
      "Bibliotek",
101
      "Bibliothek",
102
      "Biblioteek",
103
      "Bibliothèque",
104
    ].includes(props.alias.description)
105
  ) {
106
    backgroundImage = IllustrationLibrary.src;
×
107
  }
108

109
  const setBlockLevel = (blockLevel: BlockLevel) => {
140✔
110
    if (blockLevel === "none") {
1!
111
      // The back-end rejects requests trying to set this property for free users:
112
      const blockPromotionals = props.profile.has_premium ? false : undefined;
×
113
      return props.onUpdate({
×
114
        enabled: true,
115
        block_list_emails: blockPromotionals,
116
      });
117
    }
118
    if (blockLevel === "promotional") {
1!
119
      return props.onUpdate({ enabled: true, block_list_emails: true });
×
120
    }
121
    if (blockLevel === "all") {
1✔
122
      // The back-end rejects requests trying to set this property for free users:
123
      const blockPromotionals = props.profile.has_premium ? true : undefined;
1!
124
      return props.onUpdate({
1✔
125
        enabled: false,
126
        block_list_emails: blockPromotionals,
127
      });
128
    }
129
  };
130

131
  const classNames = [
140✔
132
    styles["alias-card"],
133
    props.alias.enabled ? styles["is-enabled"] : styles["is-disabled"],
140!
134
    expandButtonState.isSelected
140!
135
      ? styles["is-expanded"]
136
      : styles["is-collapsed"],
137
    props.alias.block_list_emails
140!
138
      ? styles["is-blocking-promotionals"]
139
      : styles["is-not-blocking-promotionals"],
140
    isBlockingLevelOneTrackers(props.alias, props.profile)
140!
141
      ? styles["is-removing-trackers"]
142
      : styles["is-not-removing-trackers"],
143
  ].join(" ");
144

145
  return (
146
    <div
147
      className={classNames}
148
      style={{
149
        backgroundImage: backgroundImage
140!
150
          ? `url(${backgroundImage}), none`
151
          : undefined,
152
      }}
153
    >
154
      <div className={styles["main-data"]}>
155
        <div className={styles.controls}>
156
          <TrackerRemovalIndicator
157
            alias={props.alias}
158
            profile={props.profile}
159
          />
160
          {labelEditor}
161
          <span className={styles["copy-controls"]}>
162
            <span className={styles["copy-button-wrapper"]}>
163
              <button
164
                className={styles["copy-button"]}
165
                title={l10n.getString("profile-label-click-to-copy")}
166
                aria-label={l10n.getString("profile-label-click-to-copy-alt", {
167
                  address: address,
168
                })}
169
                onClick={copyAddressToClipboard}
170
              >
171
                <samp className={styles.address}>{address}</samp>
172
                <span className={styles["copy-icon"]}>
173
                  <CopyIcon alt="" />
174
                </span>
175
              </button>
176
              <span
177
                aria-hidden={!justCopied}
178
                className={`${styles["copied-confirmation"]} ${
179
                  justCopied ? styles["is-shown"] : ""
140!
180
                }`}
181
              >
182
                {l10n.getString("profile-label-copied")}
183
              </span>
184
            </span>
185
          </span>
186
        </div>
187
        <div className={styles["block-level-label-wrapper"]}>
188
          <BlockLevelLabel alias={props.alias} />
189
        </div>
190
        {/* This <Stats> will be hidden on small screens: */}
191
        <Stats
192
          alias={props.alias}
193
          profile={props.profile}
194
          runtimeData={props.runtimeData}
195
        />
196
        <div className={styles["expand-toggle"]}>
197
          <button {...expandButtonProps} ref={expandButtonRef}>
198
            <ArrowDownIcon
199
              alt={l10n.getString(
200
                expandButtonState.isSelected
140!
201
                  ? "profile-details-collapse"
202
                  : "profile-details-expand",
203
              )}
204
              width={16}
205
              height={16}
206
            />
207
          </button>
208
        </div>
209
      </div>
210
      <div className={styles["secondary-data"]}>
211
        {/* This <Stats> will be hidden on large screens: */}
212
        <Stats
213
          alias={props.alias}
214
          profile={props.profile}
215
          runtimeData={props.runtimeData}
216
        />
217
        <div className={styles.row}>
218
          <BlockLevelSlider
219
            alias={props.alias}
220
            onChange={setBlockLevel}
221
            hasPremium={props.profile.has_premium}
222
            premiumAvailableInCountry={isPeriodicalPremiumAvailableInCountry(
223
              props.runtimeData,
224
            )}
225
          />
226
        </div>
227
        <div className={styles.row}>
228
          <dl>
229
            <div className={`${styles["forward-target"]} ${styles.metadata}`}>
230
              <dt>{l10n.getString("profile-label-forward-emails")}</dt>
231
              <dd>{props.user.email}</dd>
232
            </div>
233
            <div className={`${styles["date-created"]} ${styles.metadata}`}>
234
              <dt>{l10n.getString("profile-label-created")}</dt>
235
              <dd>{renderDate(props.alias.created_at, l10n)}</dd>
236
            </div>
237
          </dl>
238
          <AliasDeletionButton onDelete={props.onDelete} alias={props.alias} />
239
        </div>
240
      </div>
241
    </div>
242
  );
243
};
244

245
type StatsProps = {
246
  alias: AliasData;
247
  profile: ProfileData;
248
  runtimeData?: RuntimeData;
249
};
250
const Stats = (props: StatsProps) => {
2✔
251
  const l10n = useL10n();
280✔
252
  const numberFormatter = new Intl.NumberFormat(getLocale(l10n), {
280✔
253
    notation: "compact",
254
    compactDisplay: "short",
255
  });
256

257
  return (
258
    <div className={styles["alias-stats"]}>
259
      <BlockedTooltip>
260
        <span className={styles.number}>
261
          {numberFormatter.format(props.alias.num_blocked)}
262
        </span>
263
        <span className={styles.label}>
264
          {l10n.getString("profile-label-blocked")}
265
        </span>
266
      </BlockedTooltip>
267
      <ForwardedTooltip>
268
        <span className={styles.number}>
269
          {numberFormatter.format(props.alias.num_forwarded)}
270
        </span>
271
        <span className={styles.label}>
272
          {l10n.getString("profile-label-forwarded")}
273
        </span>
274
      </ForwardedTooltip>
275

276
      {/* If user is not premium, hide the replies count */}
277
      {props.profile.has_premium && (
280✔
278
        <RepliesTooltip>
279
          <span className={styles.number}>
280
            {numberFormatter.format(props.alias.num_replied)}
281
          </span>
282
          <span className={styles.label}>
283
            {l10n.getString("profile-label-replies")}
284
          </span>
285
        </RepliesTooltip>
286
      )}
287

288
      {/*
289
        If the back-end does not yet support providing tracker blocking stats,
290
        hide the blocked trackers count:
291
       */}
292
      {isFlagActive(props.runtimeData, "tracker_removal") &&
280!
293
        typeof props.alias.num_level_one_trackers_blocked === "number" && (
294
          <TrackersRemovedTooltip>
295
            <span className={styles.number}>
296
              {numberFormatter.format(
297
                props.alias.num_level_one_trackers_blocked,
298
              )}
299
            </span>
300
            <span className={styles.label}>
301
              {l10n.getString("profile-label-trackers-removed")}
302
            </span>
303
          </TrackersRemovedTooltip>
304
        )}
305
    </div>
306
  );
307
};
308

309
type TooltipProps = {
310
  children: ReactNode;
311
};
312
const ForwardedTooltip = (props: TooltipProps) => {
2✔
313
  const l10n = useL10n();
280✔
314
  const triggerState = useTooltipTriggerState({ delay: 0 });
280✔
315
  const triggerRef = useRef<HTMLSpanElement>(null);
280✔
316
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
280✔
317

318
  const { tooltipProps } = useTooltip({}, triggerState);
280✔
319

320
  return (
321
    <div className={styles["stat-wrapper"]}>
322
      <span
323
        ref={triggerRef}
324
        {...tooltipTrigger.triggerProps}
325
        className={`${styles.stat} ${styles["forwarded-stat"]}`}
326
      >
327
        {props.children}
328
      </span>
329
      {triggerState.isOpen && (
280✔
330
        <div
331
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
332
          className={styles.tooltip}
333
        >
334
          <p>
335
            <span>{l10n.getString("profile-forwarded-copy-2")}</span>
336
          </p>
337
          <p>
338
            <strong>{l10n.getString("profile-forwarded-note")}</strong>&nbsp;
339
            <span>
340
              {l10n.getString("profile-forwarded-note-copy", {
341
                size: getRuntimeConfig().emailSizeLimitNumber,
342
                unit: getRuntimeConfig().emailSizeLimitUnit,
343
              })}
344
            </span>
345
          </p>
346
        </div>
347
      )}
348
    </div>
349
  );
350
};
351

352
const BlockedTooltip = (props: TooltipProps) => {
2✔
353
  const l10n = useL10n();
280✔
354
  const triggerState = useTooltipTriggerState({ delay: 0 });
280✔
355
  const triggerRef = useRef<HTMLSpanElement>(null);
280✔
356
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
280✔
357

358
  const { tooltipProps } = useTooltip({}, triggerState);
280✔
359
  return (
360
    <div className={styles["stat-wrapper"]}>
361
      <span
362
        ref={triggerRef}
363
        {...tooltipTrigger.triggerProps}
364
        className={`${styles.stat} ${styles["blocked-stat"]}`}
365
      >
366
        {props.children}
367
      </span>
368
      {triggerState.isOpen && (
280✔
369
        <div
370
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
371
          className={styles.tooltip}
372
        >
373
          {l10n.getString("profile-blocked-copy-2")}
374
        </div>
375
      )}
376
    </div>
377
  );
378
};
379

380
const TrackersRemovedTooltip = (props: TooltipProps) => {
2✔
381
  const l10n = useL10n();
×
382
  const triggerState = useTooltipTriggerState({ delay: 0 });
×
383
  const triggerRef = useRef<HTMLSpanElement>(null);
×
384
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
×
385

386
  const { tooltipProps } = useTooltip({}, triggerState);
×
387
  return (
388
    <div className={styles["stat-wrapper"]}>
389
      <span
390
        ref={triggerRef}
391
        {...tooltipTrigger.triggerProps}
392
        className={`${styles.stat} ${styles["trackers-removed-stat"]}`}
393
      >
394
        {props.children}
395
      </span>
396
      {triggerState.isOpen && (
×
397
        <div
398
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
399
          className={styles.tooltip}
400
        >
401
          <p>{l10n.getString("profile-trackers-removed-tooltip-part1")}</p>
402
          <Localized
403
            id="profile-trackers-removed-tooltip-part2-2"
404
            elems={{
405
              b: <b />,
406
            }}
407
          >
408
            <p />
409
          </Localized>
410
        </div>
411
      )}
412
    </div>
413
  );
414
};
415

416
const RepliesTooltip = (props: TooltipProps) => {
2✔
417
  const l10n = useL10n();
88✔
418
  const triggerState = useTooltipTriggerState({ delay: 0 });
88✔
419
  const triggerRef = useRef<HTMLSpanElement>(null);
88✔
420
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
88✔
421

422
  const { tooltipProps } = useTooltip({}, triggerState);
88✔
423

424
  return (
425
    <div className={styles["stat-wrapper"]}>
426
      <span
427
        ref={triggerRef}
428
        {...tooltipTrigger.triggerProps}
429
        className={`${styles.stat} ${styles["replies-stat"]}`}
430
      >
431
        {props.children}
432
      </span>
433
      {triggerState.isOpen && (
88✔
434
        <div
435
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
436
          className={styles.tooltip}
437
        >
438
          {l10n.getString("profile-replies-tooltip")}
439
        </div>
440
      )}
441
    </div>
442
  );
443
};
444

445
type BlockLevelLabelProps = {
446
  alias: AliasData;
447
};
448
const BlockLevelLabel = (props: BlockLevelLabelProps) => {
2✔
449
  const l10n = useL10n();
140✔
450
  if (props.alias.enabled === false) {
140!
451
    return (
452
      <b
453
        className={`${styles["block-level-label"]} ${styles["block-level-all-label"]}`}
454
      >
455
        {l10n.getString("profile-promo-email-blocking-label-none")}
456
      </b>
457
    );
458
  }
459

460
  if (props.alias.block_list_emails === true) {
140!
461
    return (
462
      <b
463
        className={`${styles["block-level-label"]} ${styles["block-level-promotional-label"]}`}
464
      >
465
        {l10n.getString("profile-promo-email-blocking-label-promotionals")}
466
      </b>
467
    );
468
  }
469

470
  return null;
140✔
471
};
472

473
type TrackerRemovalIndicatorProps = {
474
  alias: AliasData;
475
  profile: ProfileData;
476
};
477
const TrackerRemovalIndicator = (props: TrackerRemovalIndicatorProps) => {
2✔
478
  const l10n = useL10n();
140✔
479
  const tooltipState = useTooltipTriggerState({ delay: 0 });
140✔
480
  const triggerRef = useRef<HTMLButtonElement>(null);
140✔
481
  const { triggerProps, tooltipProps: triggerTooltipProps } = useTooltipTrigger(
140✔
482
    {},
483
    tooltipState,
484
    triggerRef,
485
  );
486
  const { tooltipProps } = useTooltip(triggerTooltipProps, tooltipState);
140✔
487

488
  if (!isBlockingLevelOneTrackers(props.alias, props.profile)) {
140✔
489
    return null;
140✔
490
  }
491

492
  return (
493
    <span className={styles["tracker-removal-indicator-wrapper"]}>
494
      <button
495
        ref={triggerRef}
496
        {...triggerProps}
497
        aria-label={l10n.getString("profile-indicator-tracker-removal-alt")}
498
      >
499
        <HideIcon alt="" />
500
      </button>
501
      {tooltipState.isOpen && (
×
502
        <span
503
          className={styles["tracker-removal-indicator-tooltip"]}
504
          {...mergeProps(triggerTooltipProps, tooltipProps)}
505
        >
506
          {l10n.getString("profile-indicator-tracker-removal-tooltip")}
507
        </span>
508
      )}
509
    </span>
510
  );
511
};
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