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

mozilla / blurts-server / ea160b21-282b-46f8-8bfe-f53fd3b88c61

pending completion
ea160b21-282b-46f8-8bfe-f53fd3b88c61

push

circleci

GitHub
Merge pull request #2848 from mozilla/MNTOR-1188/breaches-email-count

282 of 1374 branches covered (20.52%)

Branch coverage included in aggregate %.

12 of 12 new or added lines in 4 files covered. (100.0%)

959 of 3725 relevant lines covered (25.74%)

4.18 hits per line

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

0.0
/src/client/js/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
const breachesPartial = document.querySelector("[data-partial='breaches']")
×
6
const state = new Proxy({
×
7
  selectedEmail: null,
8
  selectedStatus: 'unresolved',
9
  resolvedCount: null,
10
  unresolvedCount: null,
11
  emailCount: null,
12
  emailTotal: null
13
}, {
14
  set (target, key, value) {
15
    if (target[key] === value) return true
×
16

17
    target[key] = value
×
18
    if (key === 'selectedEmail' || key === 'selectedStatus') render()
×
19
    return true
×
20
  }
21
})
22

23
let breachesTable, breachRows, emailSelect, statusFilter, resolvedCountOutput, unresolvedCountOutput
24

25
function init () {
26
  breachesTable = breachesPartial.querySelector('.breaches-table')
×
27
  breachRows = breachesTable.querySelectorAll('.breach-row')
×
28
  emailSelect = breachesPartial.querySelector('.breaches-header custom-select')
×
29
  statusFilter = breachesPartial.querySelector('.breaches-filter')
×
30
  resolvedCountOutput = statusFilter.querySelector("label[for='breaches-resolved'] output")
×
31
  unresolvedCountOutput = statusFilter.querySelector("label[for='breaches-unresolved'] output")
×
32

33
  state.emailCount = parseInt(breachesPartial.querySelector('.email-stats').dataset.count)
×
34
  state.emailTotal = parseInt(breachesPartial.querySelector('.email-stats').dataset.total)
×
35
  state.selectedEmail = emailSelect.value // triggers render
×
36

37
  emailSelect.addEventListener('change', handleEvent)
×
38
  statusFilter.addEventListener('change', handleEvent)
×
39
  breachesTable.addEventListener('change', handleEvent)
×
40
  document.body.addEventListener('email-added', handleEvent)
×
41
}
42

43
function handleEvent (e) {
44
  switch (true) {
×
45
    case e.target.matches('custom-select[name="email-account"]'):
46
      state.selectedEmail = e.target.value
×
47
      breachesTable.querySelectorAll('span[data-email]').forEach(message => message.toggleAttribute('hidden', message.dataset.email !== e.target.value))
×
48
      break
×
49
    case e.target.matches('input[name="breaches-status"]'):
50
      state.selectedStatus = e.target.value
×
51
      statusFilter.dataset.selected = e.target.value
×
52
      break
×
53
    case e.target.matches('.resolve-list-item [type="checkbox"]'):
54
      updateBreachStatus(e.target)
×
55
      break
×
56
    case e.type === 'email-added':
57
      state.emailCount = e.detail.newEmailCount
×
58
      renderZeroState()
×
59
      break
×
60
  }
61
}
62

63
async function updateBreachStatus (input) {
64
  const affectedEmail = state.selectedEmail
×
65
  const breachId = input.name
×
66
  const checkedInputs = Array.from(input.closest('.resolve-list').querySelectorAll('input:checked'))
×
67

68
  try {
×
69
    const res = await fetch('/api/v1/user/breaches', {
×
70
      method: 'PUT',
71
      headers: {
72
        'Content-Type': 'application/json',
73
        'x-csrf-token': breachesTable.dataset.token
74
      },
75
      body: JSON.stringify({
76
        affectedEmail,
77
        breachId,
78
        resolutionsChecked: checkedInputs.map(input => input.value)
×
79
      })
80
    })
81

82
    if (!res.ok) throw new Error('Bad fetch response')
×
83

84
    const data = await res.json()
×
85
    input.closest('.breach-row').dataset.status = data[affectedEmail][breachId].isResolved ? 'resolved' : 'unresolved'
×
86
    renderResolvedCounts()
×
87
  } catch (e) {
88
    console.error('Could not update user breach resolve status:', e)
×
89
  }
90
}
91

92
function renderResolvedCounts () {
93
  state.resolvedCount = breachesPartial.querySelectorAll(`[data-status='resolved'][data-email='${state.selectedEmail}']`).length
×
94
  state.unresolvedCount = breachesPartial.querySelectorAll(`[data-status='unresolved'][data-email='${state.selectedEmail}']`).length
×
95
  resolvedCountOutput.textContent = state.resolvedCount
×
96
  unresolvedCountOutput.textContent = state.unresolvedCount
×
97
}
98

99
function renderBreachRows () {
100
  let delay = 0
×
101
  let hidden
102

103
  breachRows.forEach(breach => {
×
104
    hidden = (breach.dataset.email !== state.selectedEmail) || (breach.dataset.status !== state.selectedStatus)
×
105
    breach.toggleAttribute('hidden', hidden)
×
106
    breach.removeAttribute('open')
×
107
    if (!hidden) {
×
108
      breach.style.setProperty('--delay', `${delay}ms`)
×
109
      delay += 50
×
110
    }
111
  })
112
}
113

114
function renderZeroState () {
115
  let temp
116

117
  breachesTable.querySelector('.zero-state')?.remove()
×
118
  statusFilter.toggleAttribute('disabled', state.resolvedCount === 0 && state.unresolvedCount === 0)
×
119

120
  switch (true) {
×
121
    case state.resolvedCount === 0 && state.unresolvedCount === 0:
×
122
      temp = breachesPartial.querySelector('template.no-breaches')
×
123
      break
×
124
    case state.resolvedCount > 0 && state.unresolvedCount === 0:
×
125
      if (state.selectedStatus !== 'unresolved') return // only show zero-state on empty unresolved screen
×
126
      temp = breachesPartial.querySelector('template.all-breaches-resolved')
×
127
      break
×
128
    default:
129
      return
×
130
  }
131

132
  const content = temp.content.cloneNode(true)
×
133
  content.querySelector('.current-email').textContent = state.selectedEmail
×
134
  content.querySelector('.add-email-cta').toggleAttribute('hidden', state.emailCount >= state.emailTotal)
×
135
  breachesTable.append(content)
×
136
}
137

138
function render () {
139
  // render split into separate functions to allow independent trigger
140
  // e.g. if user marks all steps resolved – update the count, but leave the breach in place for further user interaction
141
  renderResolvedCounts()
×
142
  renderBreachRows()
×
143
  renderZeroState()
×
144
}
145

146
if (breachesPartial) init()
×
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