diff --git a/components/widget.js b/components/widget.js
index 0f23cef7..b894ebf3 100644
--- a/components/widget.js
+++ b/components/widget.js
@@ -2,12 +2,13 @@ import styled from 'styled-components'
import { size } from 'polished'
import LoadingIndicator from './loading-indicator'
import ErrorIcon from './error-icon'
+import { NONE } from '../lib/alert'
const Container = styled.div`
${size('20em')}
align-items: center;
background-color: ${props => props.theme.palette.canvasColor};
- border: 1px solid ${props => props.theme.palette.borderColor};
+ border: 1px solid ${props => props.theme.atoms.Widget[props.alertSeverity].border};
display: flex;
flex-direction: column;
justify-content: center;
@@ -19,19 +20,19 @@ const Title = styled.h1`
text-align: center;
`
-export default ({ children, error = false, loading = false, title = '' }) => {
+export default ({ children, hasError = false, isLoading = false, alertSeverity = NONE, title = '' }) => {
let content
- if (loading) {
+ if (isLoading) {
content =
- } else if (error) {
+ } else if (hasError) {
content =
} else {
content =
{children}
}
return (
-
+
{title ? {title} : ''}
{content}
diff --git a/components/widgets/bitbucket/pull-request-count.js b/components/widgets/bitbucket/pull-request-count.js
index 97810186..017da924 100644
--- a/components/widgets/bitbucket/pull-request-count.js
+++ b/components/widgets/bitbucket/pull-request-count.js
@@ -1,9 +1,10 @@
import { Component } from 'react'
import fetch from 'isomorphic-unfetch'
-import { object, string, number, array } from 'yup'
+import { array, object, string, number } from 'yup'
import Widget from '../../widget'
import Counter from '../../counter'
import { basicAuthHeader } from '../../../lib/auth'
+import { severity, NONE } from '../../../lib/alert'
const schema = object().shape({
url: string().url().required(),
@@ -12,7 +13,11 @@ const schema = object().shape({
interval: number(),
title: string(),
users: array().of(string()),
- authKey: string()
+ authKey: string(),
+ alert: array(object({
+ severity: string().required(),
+ value: number().required()
+ }))
})
export default class BitbucketPullRequestCount extends Component {
@@ -24,8 +29,9 @@ export default class BitbucketPullRequestCount extends Component {
state = {
count: 0,
- error: false,
- loading: true
+ hasError: false,
+ isLoading: true,
+ alertSeverity: NONE
}
componentDidMount () {
@@ -33,7 +39,7 @@ export default class BitbucketPullRequestCount extends Component {
.then(() => this.fetchInformation())
.catch((err) => {
console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: true, isLoading: false })
})
}
@@ -42,7 +48,7 @@ export default class BitbucketPullRequestCount extends Component {
}
async fetchInformation () {
- const { authKey, url, project, repository, users } = this.props
+ const { authKey, url, project, repository, users, alert } = this.props
const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
try {
@@ -56,19 +62,24 @@ export default class BitbucketPullRequestCount extends Component {
count = json.size
}
- this.setState({ count, error: false, loading: false })
- } catch (error) {
- this.setState({ error: true, loading: false })
+ this.setState({
+ count,
+ hasError: false,
+ isLoading: false,
+ alertSeverity: severity(count, alert)
+ })
+ } catch (err) {
+ this.setState({ hasError: true, isLoading: false, alertSeverity: NONE })
} finally {
this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
}
}
render () {
- const { count, error, loading } = this.state
+ const { count, hasError, isLoading, alertSeverity } = this.state
const { title } = this.props
return (
-
+
)
diff --git a/components/widgets/elasticsearch/hit-count.js b/components/widgets/elasticsearch/hit-count.js
index dbce320d..c18116f6 100644
--- a/components/widgets/elasticsearch/hit-count.js
+++ b/components/widgets/elasticsearch/hit-count.js
@@ -1,16 +1,21 @@
import { Component } from 'react'
import fetch from 'isomorphic-unfetch'
-import { object, string, number } from 'yup'
+import { array, object, string, number } from 'yup'
import Widget from '../../widget'
import Counter from '../../counter'
import { basicAuthHeader } from '../../../lib/auth'
+import { severity, NONE } from '../../../lib/alert'
const schema = object().shape({
url: string().url().required(),
index: string().required(),
query: string().required(),
interval: number(),
- title: string()
+ title: string(),
+ alert: array(object({
+ severity: string().required(),
+ value: number().required()
+ }))
})
export default class ElasticsearchHitCount extends Component {
@@ -21,8 +26,9 @@ export default class ElasticsearchHitCount extends Component {
state = {
count: 0,
- error: false,
- loading: true
+ hasError: false,
+ isLoading: true,
+ alertSeverity: NONE
}
componentDidMount () {
@@ -30,7 +36,7 @@ export default class ElasticsearchHitCount extends Component {
.then(() => this.fetchInformation())
.catch((err) => {
console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: true, isLoading: false })
})
}
@@ -39,26 +45,33 @@ export default class ElasticsearchHitCount extends Component {
}
async fetchInformation () {
- const { authKey, index, query, url } = this.props
+ const { authKey, index, query, url, alert } = this.props
const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
try {
const res = await fetch(`${url}/${index}/_search?q=${query}`, opts)
const json = await res.json()
+ const total = json.hits.total
- this.setState({ count: json.hits.total, error: false, loading: false })
- } catch (error) {
- this.setState({ error: true, loading: false })
+ this.setState({
+ count: total,
+ hasError: false,
+ isLoading: false,
+ alertSeverity: severity(total, alert)
+ })
+ } catch (err) {
+ this.setState({ hasError: true, isLoading: false, alertSeverity: NONE })
} finally {
this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
}
}
render () {
- const { count, error, loading } = this.state
+ const { count, hasError, isLoading, alertSeverity } = this.state
const { title } = this.props
+
return (
-
+
)
diff --git a/components/widgets/github/issue-count.js b/components/widgets/github/issue-count.js
index 3594ddad..0199456b 100644
--- a/components/widgets/github/issue-count.js
+++ b/components/widgets/github/issue-count.js
@@ -1,16 +1,21 @@
import { Component } from 'react'
import fetch from 'isomorphic-unfetch'
-import { object, string, number } from 'yup'
+import { array, object, string, number } from 'yup'
import Widget from '../../widget'
import Counter from '../../counter'
import { basicAuthHeader } from '../../../lib/auth'
+import { severity, NONE } from '../../../lib/alert'
const schema = object().shape({
owner: string().required(),
repository: string().required(),
interval: number(),
title: string(),
- authKey: string()
+ authKey: string(),
+ alert: array(object({
+ severity: string().required(),
+ value: number().required()
+ }))
})
export default class GitHubIssueCount extends Component {
@@ -21,8 +26,9 @@ export default class GitHubIssueCount extends Component {
state = {
count: 0,
- error: false,
- loading: true
+ hasError: false,
+ isLoading: true,
+ alertSeverity: NONE
}
componentDidMount () {
@@ -30,7 +36,7 @@ export default class GitHubIssueCount extends Component {
.then(() => this.fetchInformation())
.catch((err) => {
console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: true, isLoading: false })
})
}
@@ -39,26 +45,32 @@ export default class GitHubIssueCount extends Component {
}
async fetchInformation () {
- const { authKey, owner, repository } = this.props
+ const { authKey, owner, repository, alert } = this.props
const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
try {
const res = await fetch(`https://api.github.com/repos/${owner}/${repository}`, opts)
const json = await res.json()
+ const total = json.open_issues_count
- this.setState({ count: json.open_issues_count, error: false, loading: false })
- } catch (error) {
- this.setState({ error: true, loading: false })
+ this.setState({
+ count: total,
+ hasError: false,
+ isLoading: false,
+ alertSeverity: severity(total, alert)
+ })
+ } catch (err) {
+ this.setState({ hasError: true, isLoading: false, alertSeverity: NONE })
} finally {
this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
}
}
render () {
- const { count, error, loading } = this.state
+ const { count, hasError, isLoading, alertSeverity } = this.state
const { title } = this.props
return (
-
+
)
diff --git a/components/widgets/jenkins/job-status.js b/components/widgets/jenkins/job-status.js
index 03f0d04c..729306d8 100644
--- a/components/widgets/jenkins/job-status.js
+++ b/components/widgets/jenkins/job-status.js
@@ -46,8 +46,8 @@ export default class JenkinsJobStatus extends Component {
}
state = {
- loading: true,
- error: false
+ isLoading: true,
+ hasError: false
}
componentDidMount () {
@@ -55,7 +55,7 @@ export default class JenkinsJobStatus extends Component {
.then(() => this.fetchInformation())
.catch((err) => {
console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: true, isLoading: false })
})
}
@@ -82,20 +82,20 @@ export default class JenkinsJobStatus extends Component {
})
)
- this.setState({ error: false, loading: false, builds })
- } catch (error) {
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: false, isLoading: false, builds })
+ } catch (err) {
+ this.setState({ hasError: true, isLoading: false })
} finally {
this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
}
}
render () {
- const { loading, error, builds } = this.state
+ const { isLoading, hasError, builds } = this.state
const { title } = this.props
return (
-
+
{builds && builds.map(build => (
diff --git a/components/widgets/jira/issue-count.js b/components/widgets/jira/issue-count.js
index e4335528..acaabeb0 100644
--- a/components/widgets/jira/issue-count.js
+++ b/components/widgets/jira/issue-count.js
@@ -1,16 +1,21 @@
import { Component } from 'react'
import fetch from 'isomorphic-unfetch'
-import { object, string, number } from 'yup'
+import { array, object, string, number } from 'yup'
import Widget from '../../widget'
import Counter from '../../counter'
import { basicAuthHeader } from '../../../lib/auth'
+import { severity, NONE } from '../../../lib/alert'
const schema = object().shape({
url: string().url().required(),
query: string().required(),
interval: number(),
title: string(),
- authKey: string()
+ authKey: string(),
+ alert: array(object({
+ severity: string().required(),
+ value: number().required()
+ }))
})
export default class JiraIssueCount extends Component {
@@ -21,8 +26,9 @@ export default class JiraIssueCount extends Component {
state = {
count: 0,
- error: false,
- loading: true
+ hasError: false,
+ isLoading: true,
+ alertSeverity: NONE
}
componentDidMount () {
@@ -30,7 +36,7 @@ export default class JiraIssueCount extends Component {
.then(() => this.fetchInformation())
.catch((err) => {
console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: true, isLoading: false })
})
}
@@ -39,26 +45,33 @@ export default class JiraIssueCount extends Component {
}
async fetchInformation () {
- const { authKey, url, query } = this.props
+ const { authKey, url, query, alert } = this.props
const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
try {
const res = await fetch(`${url}/rest/api/2/search?jql=${query}`, opts)
const json = await res.json()
+ const total = json.total
- this.setState({ count: json.total, error: false, loading: false })
- } catch (error) {
- this.setState({ error: true, loading: false })
+ this.setState({
+ count: total,
+ hasError: false,
+ isLoading: false,
+ alertSeverity: severity(total, alert)
+ })
+ } catch (err) {
+ this.setState({ hasError: true, isLoading: false, alertSeverity: NONE })
} finally {
this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
}
}
render () {
- const { count, error, loading } = this.state
+ const { count, hasError, isLoading, alertSeverity } = this.state
const { title } = this.props
+
return (
-
+
)
diff --git a/components/widgets/jira/sprint-days-remaining.js b/components/widgets/jira/sprint-days-remaining.js
index a0feb217..7f34c5c4 100644
--- a/components/widgets/jira/sprint-days-remaining.js
+++ b/components/widgets/jira/sprint-days-remaining.js
@@ -21,8 +21,8 @@ export default class JiraSprintDaysRemaining extends Component {
state = {
days: 0,
- error: false,
- loading: true
+ hasError: false,
+ isLoading: true
}
componentDidMount () {
@@ -30,7 +30,7 @@ export default class JiraSprintDaysRemaining extends Component {
.then(() => this.fetchInformation())
.catch((err) => {
console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: true, isLoading: false })
})
}
@@ -56,19 +56,19 @@ export default class JiraSprintDaysRemaining extends Component {
const json = await res.json()
const days = this.calculateDays(json.values[0].endDate)
- this.setState({ days, error: false, loading: false })
- } catch (error) {
- this.setState({ error: true, loading: false })
+ this.setState({ days, hasError: false, isLoading: false })
+ } catch (err) {
+ this.setState({ hasError: true, isLoading: false })
} finally {
this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
}
}
render () {
- const { days, error, loading } = this.state
+ const { days, hasError, isLoading } = this.state
const { title } = this.props
return (
-
+
)
diff --git a/components/widgets/pagespeed-insights/score.js b/components/widgets/pagespeed-insights/score.js
index 47a8ca6d..a028680f 100644
--- a/components/widgets/pagespeed-insights/score.js
+++ b/components/widgets/pagespeed-insights/score.js
@@ -1,15 +1,20 @@
import { Component } from 'react'
import fetch from 'isomorphic-unfetch'
-import { object, string, number, boolean } from 'yup'
+import { array, object, string, number, boolean } from 'yup'
import CircleProgress from '../../circle-progress'
import Widget from '../../widget'
+import { severity, NONE } from '../../../lib/alert'
const schema = object().shape({
url: string().url().required(),
filterThirdPartyResources: boolean(),
interval: number(),
strategy: string(),
- title: string()
+ title: string(),
+ alert: array(object({
+ severity: string().required(),
+ value: number().required()
+ }))
})
export default class PageSpeedInsightsScore extends Component {
@@ -22,8 +27,9 @@ export default class PageSpeedInsightsScore extends Component {
state = {
score: 0,
- loading: true,
- error: false
+ isLoading: true,
+ hasError: false,
+ alertSeverity: NONE
}
componentDidMount () {
@@ -31,7 +37,7 @@ export default class PageSpeedInsightsScore extends Component {
.then(() => this.fetchInformation())
.catch((err) => {
console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: true, isLoading: false })
})
}
@@ -40,7 +46,7 @@ export default class PageSpeedInsightsScore extends Component {
}
async fetchInformation () {
- const { url, filterThirdPartyResources, strategy } = this.props
+ const { url, filterThirdPartyResources, strategy, alert } = this.props
const searchParams = [
`url=${url}`,
@@ -51,20 +57,27 @@ export default class PageSpeedInsightsScore extends Component {
try {
const res = await fetch(`https://www.googleapis.com/pagespeedonline/v2/runPagespeed?${searchParams}`)
const json = await res.json()
+ const score = json.ruleGroups.SPEED.score
- this.setState({ error: false, loading: false, score: json.ruleGroups.SPEED.score })
- } catch (error) {
- this.setState({ error: true, loading: false })
+ this.setState({
+ score,
+ hasError: false,
+ isLoading: false,
+ alertSeverity: severity(score, alert)
+ })
+ } catch (err) {
+ this.setState({ hasError: true, isLoading: false, alertSeverity: NONE })
} finally {
this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
}
}
render () {
- const { error, loading, score } = this.state
+ const { hasError, isLoading, score, alertSeverity } = this.state
const { title } = this.props
+
return (
-
+
)
diff --git a/components/widgets/pagespeed-insights/stats.js b/components/widgets/pagespeed-insights/stats.js
index e92813d0..40f53df5 100644
--- a/components/widgets/pagespeed-insights/stats.js
+++ b/components/widgets/pagespeed-insights/stats.js
@@ -22,8 +22,8 @@ export default class PageSpeedInsightsStats extends Component {
state = {
stats: {},
- loading: true,
- error: false
+ isLoading: true,
+ hasError: false
}
componentDidMount () {
@@ -31,7 +31,7 @@ export default class PageSpeedInsightsStats extends Component {
.then(() => this.fetchInformation())
.catch((err) => {
console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: true, isLoading: false })
})
}
@@ -69,19 +69,19 @@ export default class PageSpeedInsightsStats extends Component {
otherSize: this.bytesToKilobytes(pageStats.otherResponseBytes)
}
- this.setState({ error: false, loading: false, stats })
- } catch (error) {
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: false, isLoading: false, stats })
+ } catch (err) {
+ this.setState({ hasError: true, isLoading: false })
} finally {
this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
}
}
render () {
- const { error, loading, stats } = this.state
+ const { hasError, isLoading, stats } = this.state
const { title } = this.props
return (
-
+
diff --git a/components/widgets/sonarqube/index.js b/components/widgets/sonarqube/index.js
index 73f11cda..9789542a 100644
--- a/components/widgets/sonarqube/index.js
+++ b/components/widgets/sonarqube/index.js
@@ -57,8 +57,8 @@ export default class SonarQube extends Component {
state = {
measures: [],
- loading: true,
- error: false
+ isLoading: true,
+ hasError: false
}
componentDidMount () {
@@ -66,7 +66,7 @@ export default class SonarQube extends Component {
.then(() => this.fetchInformation())
.catch((err) => {
console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: true, isLoading: false })
})
}
@@ -89,9 +89,9 @@ export default class SonarQube extends Component {
const res = await fetch(`${url}/api/measures/component?componentKey=${componentKey}&metricKeys=${metricKeys}`, opts)
const json = await res.json()
- this.setState({ error: false, loading: false, measures: json.component.measures })
- } catch (error) {
- this.setState({ error: true, loading: false })
+ this.setState({ hasError: false, isLoading: false, measures: json.component.measures })
+ } catch (err) {
+ this.setState({ hasError: true, isLoading: false })
} finally {
this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
}
@@ -122,7 +122,7 @@ export default class SonarQube extends Component {
}
render () {
- const { error, loading, measures } = this.state
+ const { hasError, isLoading, measures } = this.state
const { title } = this.props
const alertStatus = this.getMetricValue(measures, 'alert_status')
@@ -136,7 +136,7 @@ export default class SonarQube extends Component {
const duplicatedLinesDensity = this.getMetricValue(measures, 'duplicated_lines_density')
return (
-
+
diff --git a/lib/alert.js b/lib/alert.js
new file mode 100644
index 00000000..b4fcc908
--- /dev/null
+++ b/lib/alert.js
@@ -0,0 +1,28 @@
+export const NONE = 'none'
+export const WARNING = 'warning'
+export const CRITICAL = 'critical'
+
+export const severity = (value, alert) => (
+ Number.isInteger(value)
+ ? severityAsInteger(value, alert)
+ : severityAsString(value, alert)
+)
+
+const severityAsInteger = (value, alert) => {
+ const alertSeverityCritical = alert.find(item => item.severity === CRITICAL)
+ if (alertSeverityCritical && value >= alertSeverityCritical.value) {
+ return CRITICAL
+ }
+
+ const alertSeverityWarning = alert.find(item => item.severity === WARNING)
+ if (alertSeverityWarning && value >= alertSeverityWarning.value) {
+ return WARNING
+ }
+
+ return NONE
+}
+
+const severityAsString = (value, alert) => {
+ const alertItem = alert.find(item => item.value === value)
+ return alertItem ? alertItem.severity : NONE
+}
diff --git a/pages/index.js b/pages/index.js
index c8637a16..6e69a281 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -29,6 +29,10 @@ export default () => (
title='JIRA Open Bugs'
url='https://crossorigin.me/https://jira.atlassian.com'
query='type=Bug AND project="Bitbucket Server" AND resolution=Unresolved ORDER BY priority DESC,created DESC'
+ alert={[
+ { severity: 'warning', value: 5 },
+ { severity: 'critical', value: 10 }
+ ]}
/>