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

mozilla / blurts-server / #13322

pending completion
#13322

push

circleci

web-flow
Merge pull request #3001 from mozilla/main

Merge `main` into `localization`

282 of 1768 branches covered (15.95%)

Branch coverage included in aggregate %.

451 of 451 new or added lines in 56 files covered. (100.0%)

959 of 4670 relevant lines covered (20.54%)

1.72 hits per line

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

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

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

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

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

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

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

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

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

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

72
  return output
×
73
}
74

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

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

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

99
  return output
×
100
}
101

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

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

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

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

131
export const breachDetail = data => `
×
132
  <header class="breach-detail-header">
133
    <div class="breach-detail-meta">
134
      <h1>
135
        ${getBreachLogo(data.breach, data.breachLogos)}
136
        ${data.breach.Title}
137
      </h1>
138
      ${getBreachCategory(data.breach) === 'website-breach'
×
139
        ? `<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>`
140
        : ''}
141
      <a href="#what-is-this-breach" class="breach-detail-meta-more-info">${getMessage(getBreachCategory(data.breach))}</a>
142
    </div>
143
  </header>
144

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

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

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

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