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

mozilla / blurts-server / 38c7cf16-2c2d-496d-bcd2-ad5d0d7c2f30

pending completion
38c7cf16-2c2d-496d-bcd2-ad5d0d7c2f30

push

circleci

Vincent
Add letter-based fallback favicons

282 of 1389 branches covered (20.3%)

Branch coverage included in aggregate %.

6 of 6 new or added lines in 1 file covered. (100.0%)

959 of 3848 relevant lines covered (24.92%)

4.14 hits per line

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

0.0
/src/views/partials/breaches.js
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4

5
import { getMessage, getLocale } from '../../utils/fluent.js'
6
import AppConstants from '../../app-constants.js'
7

8
function createEmailOptions (data, selectedEmailIndex) {
9
  const emails = data.verifiedEmails.map(obj => obj.email)
×
10
  const optionElements = emails.map((email, index) => `<option ${selectedEmailIndex === index ? 'selected' : ''}>${email}</option>`)
×
11

12
  return optionElements.join('')
×
13
}
14

15
function createBreachRows (data, logos) {
16
  const locale = getLocale()
×
17
  const shortDate = new Intl.DateTimeFormat(locale, { year: 'numeric', month: '2-digit', day: '2-digit', timeZone: 'UTC' })
×
18
  const shortList = new Intl.ListFormat(locale, { style: 'narrow' })
×
19
  const longDate = new Intl.DateTimeFormat(locale, { dateStyle: 'long', timeZone: 'UTC' })
×
20
  const longList = new Intl.ListFormat(locale, { style: 'long' })
×
21
  const breachRowsHTML = data.verifiedEmails.flatMap(account => {
×
22
    return account.breaches.map(breach => {
×
23
      const isHidden = !account.primary || breach.IsResolved // initial breach hidden state
×
24
      const status = breach.IsResolved ? 'resolved' : 'unresolved'
×
25
      const breachDate = Date.parse(breach.BreachDate)
×
26
      const addedDate = Date.parse(breach.AddedDate)
×
27
      const dataClassesTranslated = breach.DataClasses.map(item => getMessage(item))
×
28
      const description = getMessage('breach-description', {
×
29
        companyName: breach.Title,
30
        breachDate: longDate.format(breachDate),
31
        addedDate: longDate.format(addedDate),
32
        dataClasses: longList.format(dataClassesTranslated)
33
      })
34

35
      const logo = logos.has(breach.Domain)
×
36
        ? `<img src='${logos.get(breach.Domain)}' alt='' class='breach-logo' height='32' />`
37
        : `<span role="img" aria-hidden='true' class='breach-logo' style='background-color: var(${getColorForName(breach.Name)});'>${breach.Name.substring(0, 1)}</span>`
38

39
      return `
×
40
      <details class='breach-row' data-status=${status} data-email=${account.email} data-classes='${dataClassesTranslated}' ${isHidden ? 'hidden' : ''}>
×
41
        <summary>
42
          <span class='breach-company'>
43
            ${logo}
44
            ${breach.Title}
45
          </span>
46
          <span>${shortList.format(dataClassesTranslated)}</span>
47
          <span>
48
            <span class='resolution-badge is-resolved'>${getMessage('column-status-badge-resolved')}</span>
49
            <span class='resolution-badge is-active'>${getMessage('column-status-badge-active')}</span>
50
          </span>
51
          <span>${shortDate.format(addedDate)}</span>
52
        </summary>
53
        <article>
54
          <p>${description}</p>
55
          <p><strong>Resolve this breach:</strong></p>
56
          <ol class='resolve-list'>${createResolveSteps(breach)}</ol>
57
        </article>
58
      </details>
59
      `
60
    })
61
  })
62

63
  return breachRowsHTML.join('')
×
64
}
65

66
function createResolveSteps (breach) {
67
  const checkedArr = breach.ResolutionsChecked || []
×
68
  const resolveStepsHTML = Object.entries(breach.breachChecklist).map(([key, value]) => `
×
69
  <li class='resolve-list-item'>
70
    <input name='${breach.Id}' value='${key}' type='checkbox' ${checkedArr.includes(key) ? 'checked' : ''}>
×
71
    <p>${value.header}<br><i>${value.body}</i></p>
72
  </li>
73
  `)
74

75
  return resolveStepsHTML.join('')
×
76
}
77

78
export const breaches = data => `
×
79
<section>
80
  <header class='breaches-header'>
81
    <h1>${getMessage('breach-heading-email', { 'email-select': `<custom-select name='email-account'>${createEmailOptions(data.breachesData, data.selectedEmailIndex)}</custom-select>` })}</h1>
82
    <circle-chart 
83
      class='breach-chart' 
84
      title='${getMessage('breach-chart-title')}' 
85
      data-txt-other='${getMessage('other-data-class')}' 
86
      data-txt-none='${getMessage('none-data-class')}'>
87
    </circle-chart>
88
    <figure class='email-stats' data-count=${data.emailTotalCount} data-total=${AppConstants.MAX_NUM_ADDRESSES}>
89
      <img src='/images/icon-email.svg' width='55' height='30'>
90
      <figcaption>
91
        <strong>${getMessage('emails-monitored', { count: data.emailVerifiedCount, total: AppConstants.MAX_NUM_ADDRESSES })}</strong>
92
        <a href='/user/settings'>${getMessage('manage-emails-link')}</a>
93
      </figcaption>
94
    </figure>
95
  </header>
96
</section>
97
<fieldset class='breaches-filter'>
98
  <input id='breaches-unresolved' type='radio' name='breaches-status' value='unresolved' autocomplete='off' checked>
99
  <label for='breaches-unresolved'><output>&nbsp;</output>${getMessage('filter-label-unresolved')}</label>
100
  <input id='breaches-resolved' type='radio' name='breaches-status' value='resolved' autocomplete='off'>
101
  <label for='breaches-resolved'><output>&nbsp;</output>${getMessage('filter-label-resolved')}</label>
102
</fieldset>
103
<section class='breaches-table' data-token=${data.csrfToken}>
104
  <header>
105
    <span>${getMessage('column-company')}</span>
106
    <span>${getMessage('column-breached-data')}</span>
107
    ${
108
      /*
109
       * The active/resolved badge does not have a column header, but by
110
       * including an empty <span>, we can re-use the `nth-child`-based
111
       * selectors for the content columns.
112
       */
113
      '<span></span>'
114
    }
115
    <span>${getMessage('column-detected')}</span>
116
  </header>
117
  ${createBreachRows(data.breachesData, data.breachLogos)}
118
  <template class='no-breaches'>
119
    <div class="zero-state no-breaches-message">
120
      <img src='/images/breaches-none.svg' alt='' width="136" height="102" />
121
      <h2>${getMessage('breaches-none-headline')}</h2>
122
      <p>${getMessage('breaches-none-copy', { email: '<b class="current-email"></b>' })}</p>
123
      <p class='add-email-cta'>
124
        <span>${getMessage('breaches-none-cta-blurb')}</span>
125
        <button class='primary' data-dialog='add-email'>${getMessage('breaches-none-cta-button')}</button>
126
      </p>
127
    </div>
128
  </template>
129
  <template class='all-breaches-resolved'>
130
    <div class="zero-state all-breaches-resolved-message">
131
      <img src='/images/breaches-all-resolved.svg' alt='' width="136" height="102" />
132
      <h2>${getMessage('breaches-all-resolved-headline')}</h2>
133
      <p>${getMessage('breaches-all-resolved-copy', { email: '<b class="current-email"></b>' })}</p>
134
      <p class='add-email-cta'>
135
        <span>${getMessage('breaches-all-resolved-cta-blurb')}</span>
136
        <button class='primary' data-dialog='add-email'>${getMessage('breaches-all-resolved-cta-button')}</button>
137
      </p>
138
    </div>
139
  </template>
140
</section>
141
`
142

143
/**
144
 * @param {string} name
145
 * @returns string CSS variable for a string-specific color
146
 */
147
function getColorForName (name) {
148
  const logoColors = [
×
149
    '--blue-5',
150
    '--purple-5',
151
    '--green-05',
152
    '--violet-5',
153
    '--orange-5',
154
    '--yellow-5',
155
    '--red-5',
156
    '--pink-5'
157
  ]
158

159
  const charValue = name
×
160
    .split('')
161
    .map(letter => letter.codePointAt(0))
×
162
    .reduce((sum, codePoint) => sum + codePoint)
×
163

164
  return logoColors[charValue % logoColors.length]
×
165
}
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