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

hasadna / open-bus-map-search / 14284601510

05 Apr 2025 06:23PM UTC coverage: 80.635% (-0.5%) from 81.115%
14284601510

Pull #1057

github

web-flow
Merge 6ff654cd9 into 01b778f68
Pull Request #1057: chore(deps-dev): bump eslint-config-prettier from 9.1.0 to 10.1.1

351 of 498 branches covered (70.48%)

Branch coverage included in aggregate %.

994 of 1170 relevant lines covered (84.96%)

87161.89 hits per line

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

84.91
/src/pages/about/index.tsx
1
import styled from 'styled-components'
2
import { Trans, useTranslation } from 'react-i18next'
3
import Typography from '@mui/material/Typography'
4
import Stack from '@mui/material/Stack'
5
import './About.scss'
6
import { useQuery } from '@tanstack/react-query'
7
import SlackIcon from '../../resources/slack-icon.svg'
8
import { VersionInfo } from './version/VersionInfo'
9
import Widget from 'src/shared/Widget'
10

11
const pageName = 'aboutPage'
36✔
12
const About = () => {
36✔
13
  const { t } = useTranslation()
88✔
14
  return (
15
    <AboutStyle>
16
      <Stack spacing={4}>
17
        <Typography variant="h4" gutterBottom className="page-title">
18
          {t(`${pageName}.title`)}
19
        </Typography>
20
        <WhatIsWebsite />
21
        <YoutubePlaylist />
22
        <DiscoveredMistake />
23
        <Privacy />
24
        <License />
25
        <Questions />
26
        <Funding />
27
        <Attributions />
28
        <VersionInfo />
29
        <Contributors />
30
      </Stack>
31
    </AboutStyle>
32
  )
33
}
34

35
const WhatIsWebsite = () => {
36✔
36
  const { t } = useTranslation()
88✔
37

38
  return (
39
    <Widget>
40
      <h2>{t('what_is_website')}</h2>
41
      <p>{t('what_is_website_paragraph')}</p>
42
      <ul style={{ listStyle: 'disc', paddingRight: '40px' }}>
43
        <li>{t('planning_information')}</li>
44
        <li>{t('performance_information')}</li>
45
      </ul>
46
    </Widget>
47
  )
48
}
49
const YoutubePlaylist = () => {
36✔
50
  return (
51
    <iframe
52
      width="560"
53
      height="315"
54
      src="https://www.youtube.com/embed/videoseries?si=oTULlxq8Is188hPu&amp;list=PL6Rh06rT7uiX1AQE-lm55hy-seL3idx3T"
55
      title="YouTube video player"
56
      frameBorder="0"
57
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
58
      referrerPolicy="strict-origin-when-cross-origin"
59
      allowFullScreen></iframe>
60
  )
61
}
62
const DiscoveredMistake = () => {
36✔
63
  const { t } = useTranslation()
88✔
64

65
  return (
66
    <Widget>
67
      <h2>{t('discovered_mistake')}</h2>
68
      <p>{t('discovered_mistake_paragraph')}</p>
69
    </Widget>
70
  )
71
}
72

73
const Privacy = () => {
36✔
74
  const { t } = useTranslation()
88✔
75
  const googlAnalyticsUrl = 'https://marketingplatform.google.com/about/analytics/'
88✔
76
  const googleAnaliticsPrivacyUrl = 'https://support.google.com/analytics/answer/6004245?hl=iw'
88✔
77
  return (
78
    <Widget>
79
      <h2>{t('privacy')}</h2>
80
      <p>
81
        <Trans i18nKey="aboutPage.privacyText">
82
          <a href={googlAnalyticsUrl}></a>
83
          <a href={googleAnaliticsPrivacyUrl}></a>
84
        </Trans>
85
      </p>
86
    </Widget>
87
  )
88
}
89

90
const License = () => {
36✔
91
  const { t } = useTranslation()
88✔
92
  const licenseLink = 'https://creativecommons.org/licenses/by-sa/4.0/'
88✔
93
  const licenseOrgLink = 'https://creativecommons.org/'
88✔
94
  return (
95
    <Widget>
96
      <h2>{t('license')}</h2>
97
      <p>
98
        <Trans
99
          i18nKey="aboutPage.licenseInfo.text"
100
          values={{ licenseName: t('aboutPage.licenseInfo.licenseName') }}>
101
          <a href={licenseLink}></a>
102
          <a href={licenseOrgLink}></a>
103
        </Trans>
104
      </p>
105
    </Widget>
106
  )
107
}
108

109
const Questions = () => {
36✔
110
  const { t } = useTranslation()
88✔
111
  const linksTextPath = `${pageName}.contactLinksText`
88✔
112
  return (
113
    <Widget>
114
      <h2>{t('questions')}</h2>
115
      <ul>
116
        <li>
117
          <a href="https://www.hasadna.org.il/%D7%A6%D7%95%D7%A8-%D7%A7%D7%A9%D7%A8/">
118
            {t(`${linksTextPath}.sadna`)}
119
          </a>
120
        </li>
121
        <li>
122
          <img src={SlackIcon} alt="Slack icon" />
123
          <a href="https://hasadna.slack.com/join/shared_invite/zt-167h764cg-J18ZcY1odoitq978IyMMig#/shared-invite/email">
124
            {t(`${linksTextPath}.slack`)}
125
          </a>
126
        </li>
127
        <li>
128
          <a href="https://www.jgive.com/new/he/ils/donation-targets/3268#donation-modal">
129
            {t(`${linksTextPath}.donations`)}
130
          </a>
131
        </li>
132
      </ul>
133
    </Widget>
134
  )
135
}
136

137
const Funding = () => {
36✔
138
  const { t } = useTranslation()
88✔
139

140
  return (
141
    <Widget>
142
      <h2>{t('funding')}</h2>
143
      <div>
144
        <p>
145
          {t('funding_paragraph')}&nbsp;
146
          <a href="https://open-bus-stride-api.hasadna.org.il/docs">Open API</a>
147
        </p>
148
      </div>
149
      <ul>
150
        <li>{t('mr_meir')}</li>
151
        <li>{t('innovation_authority')}</li>
152
        <li>{t('migdal_company')}</li>
153
        <li>
154
          <a href="https://www.jgive.com/new/he/ils/donation-targets/3268#donation-modal">
155
            {t('and_smaller_donors')}
156
          </a>
157
        </li>
158
      </ul>
159
    </Widget>
160
  )
161
}
162

163
const Attributions = () => {
36✔
164
  return (
165
    <Widget>
166
      <h2>Attributions</h2>
167
      <ul>
168
        <li>
169
          Thanks <a href="http://www.applitools.com/">Applitools</a> for the free open-source
170
          license for their visual testing tool
171
        </li>
172
        <li>
173
          Bus ifmage by{' '}
174
          <a
175
            href="https://www.freepik.com/free-vector/passengers-waiting-bus-city-queue-town-road-flat-vector-illustration-public-transport-urban-lifestyle_10173277.htm#query=public%20transportation&position=0&from_view=search&track=ais&uuid=70a79b38-20cb-42b8-9dde-b96a68088522"
176
            target="_blank"
177
            rel="noopener noreferrer nofollow">
178
            pch.vector
179
          </a>{' '}
180
          on Freepik
181
        </li>
182
      </ul>
183
    </Widget>
184
  )
185
}
186

187
const Contributors = () => {
36✔
188
  const { t } = useTranslation()
116✔
189
  const { contributors, isLoading, isError } = useContributions()
116✔
190

191
  return (
192
    <Widget>
193
      <h2>{t('aboutPage.contributors')}</h2>
194
      <p>
195
        {t('aboutPage.contributorsText')}
196
        <br />
197
        <Trans i18nKey="aboutPage.contributorsReadMore">
198
          <a href="https://github.com/hasadna/open-bus-map-search/blob/main/CONTRIBUTING.md"></a>
199
        </Trans>
200
      </p>
201
      <ul className="contributions">
202
        {isLoading && <p>Loading...</p>}
116✔
203
        {isError && <p>Error...</p>}
116✔
204
        {contributors &&
116✔
205
          contributors.map((author) => (
206
            <li key={author.id}>
207
              <a href={author.html_url}>
208
                <h2>{author.login}</h2>
209
                <img src={author.avatar_url} alt={author.login} />
210
                <p>
211
                  {author.contributions} {t('aboutPage.contributions')}
212
                </p>
213
              </a>
214
            </li>
215
          ))}
216
      </ul>
217
    </Widget>
218
  )
219
}
220

221
const AboutStyle = styled.div`
36✔
222
  display: flex;
223
  flex-direction: column;
224
  padding: 0 1rem;
225
  & .about-center-container {
226
    width: 100%;
227
    max-width: 770px;
228
    & h1 {
229
      font-size: 2em;
230
    }
231
  }
232
`
233
function useContributions(start: Date = new Date('2023-01-01'), end: Date = new Date()) {
232✔
234
  const owner = 'hasadna'
116✔
235
  const repos = [
116✔
236
    'open-bus-map-search',
237
    'open-bus-stride-api',
238
    'open-bus-backend',
239
    'open-bus-pipelines',
240
    'open-bus-siri-requester',
241
    'open-bus-gtfs-etl',
242
    'open-bus-stride-etl',
243
  ]
244

245
  const apis = repos.map(
116✔
246
    (repo) =>
247
      `https://api.github.com/repos/${owner}/${repo}/contributors?order=desc&until=${end.toISOString()}&since=${start.toISOString()}`,
812✔
248
  )
249

250
  const { data, isLoading, isError } = useQuery({
116✔
251
    queryKey: ['contributors'],
252
    queryFn: () =>
253
      Promise.all(
34✔
254
        apis.map((api) =>
255
          fetch(api)
238✔
256
            .then((res) => res.json())
32✔
257
            .catch(() => ({})),
42✔
258
        ),
259
      ),
260
    gcTime: Infinity,
261
    staleTime: 3 * 24 * 60 * 60 * 1000, // refresh the cached data every 3 days
262
    refetchOnWindowFocus: false,
263
    refetchOnReconnect: false,
264
    refetchOnMount: false,
265
    networkMode: 'offlineFirst',
266
  })
267

268
  try {
116✔
269
    const contributors = (data?.flat() as Author[])
116✔
270
      // filter repos with no contributors
271
      .filter(Boolean)
272
      // filter out bots
273
      .filter((a) => a.type === 'User')
336✔
274
      // sort by contributions
275
      .sort((a: Author, b: Author) => b.contributions - a.contributions)
×
276
      .reduce(combineAuthor, [] as Author[])
277
    return { contributors, isLoading, isError }
48✔
278
  } catch (error) {
279
    return { contributors: [] as const, isLoading: false, isError: true }
68✔
280
  }
281
}
282

283
// sum contributions of the same user
284
function combineAuthor(authors: Author[], author: Author) {
285
  const sameUser = authors.find((a) => a.login === author.login)
×
286
  if (!sameUser) {
×
287
    authors.push(author)
×
288
  } else {
289
    sameUser.contributions += author.contributions
×
290
  }
291
  return authors
×
292
}
293

294
type Author = {
295
  avatar_url: string
296
  contributions: number
297
  html_url: string
298
  id: number
299
  login: string
300
  node_id: string
301
  organizations_url: string
302
  received_events_url: string
303
  repos_url: string
304
  site_admin: boolean
305
  starred_url: string
306
  subscriptions_url: string
307
  type: string
308
  url: string
309
}
310

311
export default About
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