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

mozilla / blurts-server / #12934

pending completion
#12934

push

circleci

web-flow
Merge pull request #2917 from mozilla/MNTOR-1343-breach-page-styling

List breaches

282 of 1595 branches covered (17.68%)

Branch coverage included in aggregate %.

96 of 96 new or added lines in 10 files covered. (100.0%)

959 of 4309 relevant lines covered (22.26%)

1.85 hits per line

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

0.0
/src/views/partials/breach-detail.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 { getLocale, getMessage } from '../../utils/fluent.js'
6
import { getAllPriorityDataClasses, getAllGenericRecommendations } from '../../utils/recommendations.js'
7

8
function getBreachCategory (breach) {
9
  if (['Exactis', 'Apollo', 'YouveBeenScraped', 'ElasticsearchSalesLeads', 'Estonia', 'MasterDeeds', 'PDL'].includes(breach.Name)) {
×
10
    return 'data-aggregator-breach'
×
11
  }
12
  if (breach.IsSensitive) {
×
13
    return 'sensitive-breach'
×
14
  }
15
  if (breach.Domain !== '') {
×
16
    return 'website-breach'
×
17
  }
18
  return 'data-aggregator-breach'
×
19
}
20

21
function compareBreachDates (breach) {
22
  const breachDate = new Date(breach.BreachDate)
×
23
  const addedDate = new Date(breach.AddedDate)
×
24
  const timeDiff = Math.abs(breachDate.getTime() - addedDate.getTime())
×
25
  const dayDiff = Math.ceil(timeDiff / (1000 * 3600 * 24))
×
26
  if (dayDiff > 90) {
×
27
    return true
×
28
  }
29
  return false
×
30
}
31

32
function getSortedDataClasses (breach, isUserBrowserFirefox = false, isUserLocaleEnUs = false, isUserLocalEn = false, changePWLink = false) {
×
33
  const priorityDataClasses = getAllPriorityDataClasses(isUserBrowserFirefox, isUserLocaleEnUs, changePWLink)
×
34

35
  const sortedDataClasses = {
×
36
    priority: [],
37
    lowerPriority: []
38
  }
39

40
  const dataClasses = breach.DataClasses
×
41
  dataClasses.forEach(dataClass => {
×
42
    const dataType = getMessage(dataClass)
×
43
    if (priorityDataClasses[dataClass]) {
×
44
      priorityDataClasses[dataClass].dataType = dataType
×
45
      sortedDataClasses.priority.push(priorityDataClasses[dataClass])
×
46
      return
×
47
    }
48
    sortedDataClasses.lowerPriority.push(dataType)
×
49
  })
50
  sortedDataClasses.priority.sort((a, b) => { return b.weight - a.weight })
×
51
  sortedDataClasses.lowerPriority = sortedDataClasses.lowerPriority.join(', ')
×
52
  return sortedDataClasses
×
53
}
54

55
function makeDataSection (breach) {
56
  const dataClasses = getSortedDataClasses(breach)
×
57

58
  let output = dataClasses.priority.map(dataClass =>
×
59
    `<li>
×
60
      <img src="${dataClass.pathToGlyph}.svg" width="24" alt="">
61
      ${dataClass.dataType}
62
    </li>`
63
  ).join('')
64

65
  output +=
×
66
    `<li>
67
      <img src="/images/more.svg">
68
      ${dataClasses.lowerPriority}
69
    </li>`
70

71
  return output
×
72
}
73

74
function makeRecommendationCards (breach) {
75
  const dataClasses = getSortedDataClasses(breach)
×
76

77
  let output = dataClasses.priority.map(dataClass =>
×
78
    dataClass.recommendations?.map(r =>
×
79
    `<div class="breach-detail-recommendation ${r.recIconClassName}">
×
80
      <dt>${getMessage(r.recommendationCopy.subhead)}</dt>
81
      <dd>
82
        <p>${getMessage(r.recommendationCopy.body)}</p>
83
        ${r.recommendationCopy.cta ? `<a href="${r.ctaHref}" target="${r.ctaShouldOpenInNewTab ? '_blank' : '_self'}">${getMessage(r.recommendationCopy.cta)}</a>` : ''}
×
84
      </dd>
85
    </div>`).join('')
86
  ).join('')
87

88
  output += getAllGenericRecommendations().map(dataClass =>
×
89
    `<div class="breach-detail-recommendation ${dataClass.recIconClassName}">
×
90
      <dt>${getMessage(dataClass.recommendationCopy.subhead)}</dt>
91
      <dd>
92
        <p>${getMessage(dataClass.recommendationCopy.body)}</p>
93
        ${dataClass.recommendationCopy.cta ? `<a href="${dataClass.ctaHref}" target="${dataClass.ctaShouldOpenInNewTab ? '_blank' : '_self'}">${getMessage(dataClass.recommendationCopy.cta)}</a>` : ''}
×
94
      </dd>
95
    </div>`
96
  ).join('')
97

98
  return output
×
99
}
100

101
function getBreachDetail (categoryId) {
102
  if (categoryId === 'data-aggregator-breach') {
×
103
    return {
×
104
      subhead: getMessage('what-is-data-agg'),
105
      body: getMessage('what-is-data-agg-blurb')
106

107
    }
108
  } else if (categoryId === 'sensitive-breach') {
×
109
    return {
×
110
      subhead: getMessage('sensitive-sites'),
111
      body: getMessage('sensitive-sites-copy')
112

113
    }
114
  } else {
115
    return {
×
116
      subhead: getMessage('what-is-a-website-breach'),
117
      body: getMessage('website-breach-blurb')
118
    }
119
  }
120
}
121

122
function makeBreachDetails (breach) {
123
  const breachDetail = getBreachDetail(breach)
×
124
  return `
×
125
  <h2>${breachDetail.subhead}</h2>
126
  <div>${breachDetail.body}</div>
127
  `
128
}
129

130
export const breachDetails = data => `
×
131
  <header class="breach-detail-header">
132
    <img class="breach-detail-logo breach-logo" alt="" src="https://monitor.cdn.mozilla.net/img/logos/${data.breach.LogoPath}" />
133
    <div class="breach-detail-meta">
134
      <h1>${data.breach.Title}</h1>
135
      ${getBreachCategory(data.breach) === 'website-breach'
×
136
        ? `<a href="https://${data.breach.Domain}" class="breach-detail-meta-domain" rel="nofollow noopener noreferrer" data-event-label="${data.breach.Domain}" data-event-action="Engage" data-event-category="Breach Detail: Website URL Link" target="_blank">${data.breach.Domain}</a>`
137
        : ''}
138
      <a href="#what-is-this-breach" class="breach-detail-meta-more-info">${getMessage(getBreachCategory(data.breach))}</a>
139
    </div>
140
  </header>
141

142
  <!-- Overview -->
143
  <div class="breach-detail-overview">
144
    <div class="breach-detail-overview-blurb">
145
      <h2>${getMessage('breach-overview-title')}</h2>
146
      <div>${getMessage(
147
        'breach-overview-new',
148
        {
149
          breachDate: data.breach.BreachDate.toLocaleString(getLocale(), { year: 'numeric', month: 'long', day: 'numeric' }),
150
          breachTitle: data.breach.Name,
151
          addedDate: data.breach.AddedDate.toLocaleString(getLocale(), { year: 'numeric', month: 'long', day: 'numeric' })
152
        })}</div>
153
      ${compareBreachDates(data.breach) ? `<a href="#delayed-reporting">${getMessage('delayed-reporting-headline')}</a>` : ''}
×
154
    </div>
155
    <!--Exposed Data Classes -->
156
    <div>
157
      <h3>${getMessage('what-data')}</h3>
158
      <ul class="breach-detail-compromised-list">
159
        ${makeDataSection(data.breach)}
160
      </ul>
161
      <p class="breach-detail-attribution">
162
      ${getMessage('email-2022-hibp-attribution', {
163
        'hibp-link-attr': 'href="https://haveibeenpwned.com/" target="_blank"'
164
      })}
165
      </p>
166
    </div>
167
  </div>
168

169
  <!-- Sign Up Banner -->
170
  <div class="breach-detail-sign-up">
171
    <img alt="" src="/images/breach-detail-scan.svg" width="120" />
172
    <div class="breach-detail-sign-up-content">
173
        <h2>${getMessage('find-out-if-2')}</h2>
174
        <span>${getMessage('find-out-if-description')}</span>
175
    </div>
176
    <div class="breach-detail-sign-up-cta-wrapper">
177
      <a href="/user/breaches" class="button primary">${getMessage('breach-detail-cta-signup')}</a>
178
    </div>
179
  </div>
180

181
  <!-- What to do tips -->
182
  <div id="what-to-do-next">
183
    <div class="breach-detail-recommendation-lead">
184
      <h2>${data.breach.DataClasses.includes('passwords') ? getMessage('rec-section-headline') : getMessage('rec-section-headline-no-pw')}</h2>
×
185
      <p>${data.breach.DataClasses.includes('passwords') ? getMessage('rec-section-subhead') : getMessage('rec-section-subhead-no-pw')}</p>
×
186
    </div>
187
    <dl class="breach-detail-recommendation-list">
188
      ${makeRecommendationCards(data.breach)}
189
    </dl>
190
  </div>
191

192
  <!-- What is this breach? / Why did it take you so long to report it?-->
193
  <div class="breach-detail-info">
194
    <div id="what-is-this-breach">
195
      ${makeBreachDetails(getBreachCategory(data.breach))}
196
    </div>
197
    ${compareBreachDates(data.breach)
×
198
      ? `
199
        <div id="delayed-reporting">
200
          <h2>${getMessage('delayed-reporting-headline')}</h2>
201
          ${getMessage('delayed-reporting-copy')}
202
        </div>`
203
      : ''
204
    }
205
  </div>
206
`
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