From 7fa40e472eb6a5c7b340c482e55499b3e739b001 Mon Sep 17 00:00:00 2001
From: HenningThiemann <32903255+HenningThiemann@users.noreply.github.com>
Date: Thu, 26 Mar 2020 08:11:08 +0100
Subject: [PATCH] Add typescript suppport
* first files migrated to typescript
* switched all components to typescript
* moved typescript interfaces to separate files
---
.github/workflows/nodejs.yml | 1 +
.gitignore | 4 +
README.md | 30 +--
auth.js | 26 ---
auth.ts | 40 ++++
components/Badge.tsx | 12 +
components/CircleProgress.tsx | 71 ++++++
components/Counter.tsx | 9 +
components/Dashboard.tsx | 48 ++++
components/ErrorIcon.tsx | 15 ++
components/Link.tsx | 6 +
components/LoadingIndicator.tsx | 56 +++++
components/{table.js => Table.tsx} | 8 +-
components/Widget.tsx | 48 ++++
components/badge.js | 12 -
components/circle-progress.js | 47 ----
components/counter.js | 10 -
components/dashboard.js | 41 ----
components/error-icon.js | 14 --
components/link.js | 6 -
components/loading-indicator.js | 54 -----
components/widget.js | 39 ----
.../bitbucket/BitbucketPullRequestCount.tsx | 78 +++++++
.../widgets/bitbucket/bitbucket-model.ts | 15 ++
.../widgets/bitbucket/pull-request-count.js | 76 -------
components/widgets/datetime/DateTime.tsx | 54 +++++
components/widgets/datetime/datetime-model.ts | 7 +
components/widgets/datetime/index.js | 48 ----
.../elasticsearch/ElasticsearchHitCount.tsx | 67 ++++++
.../elasticsearch/elasticsearch-model.ts | 14 ++
components/widgets/elasticsearch/hit-count.js | 66 ------
.../widgets/github/GitHubIssueCount.tsx | 72 ++++++
components/widgets/github/github-model.ts | 13 ++
components/widgets/github/issue-count.js | 66 ------
.../widgets/jenkins/JenkinsBuildBuration.tsx | 137 ++++++++++++
.../widgets/jenkins/JenkinsJobHealth.tsx | 125 +++++++++++
.../widgets/jenkins/JenkinsJobStatus.tsx | 126 +++++++++++
components/widgets/jenkins/build-duration.js | 122 -----------
components/widgets/jenkins/jenkins-model.ts | 53 +++++
components/widgets/jenkins/job-health.js | 121 -----------
components/widgets/jenkins/job-status.js | 120 ----------
components/widgets/jira/JiraIssueCount.tsx | 64 ++++++
.../widgets/jira/JiraSprintDaysRemaining.tsx | 80 +++++++
components/widgets/jira/issue-count.js | 66 ------
components/widgets/jira/jira-model.ts | 27 +++
.../widgets/jira/sprint-days-remaining.js | 76 -------
.../PageSpeedInsideScore.tsx | 79 +++++++
.../PageSpeedInsightsStats.tsx | 153 +++++++++++++
.../pagespeed-insights/pagespeed-model.ts | 39 ++++
.../widgets/pagespeed-insights/score.js | 72 ------
.../widgets/pagespeed-insights/stats.js | 121 -----------
components/widgets/sonarqube/SonarQube.tsx | 205 ++++++++++++++++++
components/widgets/sonarqube/index.js | 182 ----------------
.../widgets/sonarqube/sonarqube-model.ts | 13 ++
.../widgets/title/{index.js => Title.tsx} | 6 +-
lib/auth-model.ts | 4 +
lib/auth.js | 13 --
lib/auth.ts | 16 ++
next-env.d.ts | 2 +
next.config.js | 12 +-
package.json | 11 +-
pages/MyDocument.tsx | 31 +++
pages/_document.js | 24 --
pages/index.js | 82 -------
pages/index.tsx | 92 ++++++++
styles/dark-theme.js | 32 ---
styles/dark-theme.ts | 36 +++
styles/light-theme.js | 32 ---
styles/light-theme.ts | 36 +++
styles/theme-model.ts | 15 ++
tsconfig.json | 29 +++
yarn.lock | 33 +++
72 files changed, 2060 insertions(+), 1600 deletions(-)
delete mode 100644 auth.js
create mode 100644 auth.ts
create mode 100644 components/Badge.tsx
create mode 100644 components/CircleProgress.tsx
create mode 100644 components/Counter.tsx
create mode 100644 components/Dashboard.tsx
create mode 100644 components/ErrorIcon.tsx
create mode 100644 components/Link.tsx
create mode 100644 components/LoadingIndicator.tsx
rename components/{table.js => Table.tsx} (76%)
create mode 100644 components/Widget.tsx
delete mode 100644 components/badge.js
delete mode 100644 components/circle-progress.js
delete mode 100644 components/counter.js
delete mode 100644 components/dashboard.js
delete mode 100644 components/error-icon.js
delete mode 100644 components/link.js
delete mode 100644 components/loading-indicator.js
delete mode 100644 components/widget.js
create mode 100644 components/widgets/bitbucket/BitbucketPullRequestCount.tsx
create mode 100644 components/widgets/bitbucket/bitbucket-model.ts
delete mode 100644 components/widgets/bitbucket/pull-request-count.js
create mode 100644 components/widgets/datetime/DateTime.tsx
create mode 100644 components/widgets/datetime/datetime-model.ts
delete mode 100644 components/widgets/datetime/index.js
create mode 100644 components/widgets/elasticsearch/ElasticsearchHitCount.tsx
create mode 100644 components/widgets/elasticsearch/elasticsearch-model.ts
delete mode 100644 components/widgets/elasticsearch/hit-count.js
create mode 100644 components/widgets/github/GitHubIssueCount.tsx
create mode 100644 components/widgets/github/github-model.ts
delete mode 100644 components/widgets/github/issue-count.js
create mode 100644 components/widgets/jenkins/JenkinsBuildBuration.tsx
create mode 100644 components/widgets/jenkins/JenkinsJobHealth.tsx
create mode 100644 components/widgets/jenkins/JenkinsJobStatus.tsx
delete mode 100644 components/widgets/jenkins/build-duration.js
create mode 100644 components/widgets/jenkins/jenkins-model.ts
delete mode 100644 components/widgets/jenkins/job-health.js
delete mode 100644 components/widgets/jenkins/job-status.js
create mode 100644 components/widgets/jira/JiraIssueCount.tsx
create mode 100644 components/widgets/jira/JiraSprintDaysRemaining.tsx
delete mode 100644 components/widgets/jira/issue-count.js
create mode 100644 components/widgets/jira/jira-model.ts
delete mode 100644 components/widgets/jira/sprint-days-remaining.js
create mode 100644 components/widgets/pagespeed-insights/PageSpeedInsideScore.tsx
create mode 100644 components/widgets/pagespeed-insights/PageSpeedInsightsStats.tsx
create mode 100644 components/widgets/pagespeed-insights/pagespeed-model.ts
delete mode 100644 components/widgets/pagespeed-insights/score.js
delete mode 100644 components/widgets/pagespeed-insights/stats.js
create mode 100644 components/widgets/sonarqube/SonarQube.tsx
delete mode 100644 components/widgets/sonarqube/index.js
create mode 100644 components/widgets/sonarqube/sonarqube-model.ts
rename components/widgets/title/{index.js => Title.tsx} (63%)
create mode 100644 lib/auth-model.ts
delete mode 100644 lib/auth.js
create mode 100644 lib/auth.ts
create mode 100644 next-env.d.ts
create mode 100644 pages/MyDocument.tsx
delete mode 100644 pages/_document.js
delete mode 100644 pages/index.js
create mode 100644 pages/index.tsx
delete mode 100644 styles/dark-theme.js
create mode 100644 styles/dark-theme.ts
delete mode 100644 styles/light-theme.js
create mode 100644 styles/light-theme.ts
create mode 100644 styles/theme-model.ts
create mode 100644 tsconfig.json
diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 89aa9c27..40ee8d2c 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -21,6 +21,7 @@ jobs:
run: |
yarn install
yarn build
+ yarn run format
yarn test
env:
CI: true
diff --git a/.gitignore b/.gitignore
index f615fae0..74e2c71f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,3 +59,7 @@ typings/
# Next.js directory
.next
+
+# IntelliJ files
+/.idea/
+*.iml
diff --git a/README.md b/README.md
index 4536dda0..e17bd802 100644
--- a/README.md
+++ b/README.md
@@ -102,7 +102,7 @@ For an example, see [pages/index.js](./pages/index.js).
## Available Widgets
-### [DateTime](./components/widgets/datetime/index.js)
+### [DateTime](components/widgets/datetime/DateTime.tsx)
#### Example
@@ -116,7 +116,7 @@ import DateTime from '../components/widgets/datetime'
* `interval`: Refresh interval in milliseconds (Default: `10000`)
-### [Jenkins Job Status](./components/widgets/jenkins/job-status.js)
+### [Jenkins Job Status](components/widgets/jenkins/JenkinsJobStatus.tsx)
#### Example
@@ -142,7 +142,7 @@ For Jenkins multibranch projects add `branch` to the object.
* `jobs`: List of all jobs
* `authKey`: Credential key, defined in [auth.js](./auth.js)
-### [Jenkins Job Health](./components/widgets/jenkins/job-health.js)
+### [Jenkins Job Health](components/widgets/jenkins/JenkinsJobHealth.tsx)
#### Example
@@ -169,7 +169,7 @@ For Jenkins multibranch projects add `branch` to the object.
* `authKey`: Credential key, defined in [auth.js](./auth.js)
-### [Jenkins Build Duration](./components/widgets/jenkins/build-duration.js)
+### [Jenkins Build Duration](components/widgets/jenkins/JenkinsBuildBuration.tsx)
#### Example
@@ -195,7 +195,7 @@ For Jenkins multibranch projects add `branch` to the object.
* `jobs`: List of all jobs
* `authKey`: Credential key, defined in [auth.js](./auth.js)
-### [JIRA Issue Count](./components/widgets/jira/issue-count.js)
+### [JIRA Issue Count](components/widgets/jira/JiraIssueCount.tsx)
#### Example
@@ -219,7 +219,7 @@ For Jenkins multibranch projects add `branch` to the object.
* `query`: JIRA search query (`jql`)
* `authKey`: Credential key, defined in [auth.js](./auth.js)
-### [JIRA Sprint Days Remaining](./components/widgets/jira/sprint-days-remaining.js)
+### [JIRA Sprint Days Remaining](components/widgets/jira/JiraSprintDaysRemaining.tsx)
#### Example
@@ -241,7 +241,7 @@ import JiraSprintDaysRemaining from '../components/widgets/jira/sprint-days-rema
* `boardId`: JIRA board id
* `authKey`: Credential key, defined in [auth.js](./auth.js)
-### [Bitbucket PullRequest Count](./components/widgets/bitbucket/pull-request-count.js)
+### [Bitbucket PullRequest Count](components/widgets/bitbucket/BitbucketPullRequestCount.tsx)
#### Example
@@ -267,7 +267,7 @@ import BitbucketPullRequestCount from '../components/widgets/bitbucket/pull-requ
* `users`: Bitbucket user slugs as an array
* `authKey`: Credential key, defined in [auth.js](./auth.js)
-### [PageSpeed Insights Score](./components/widgets/pagespeed-insights/score.js)
+### [PageSpeed Insights Score](components/widgets/pagespeed-insights/PageSpeedInsideScore.tsx)
#### Example
@@ -286,7 +286,7 @@ import PageSpeedInsightsScore from '../components/widgets/pagespeed-insights/sco
* Acceptable values: `desktop` | `mobile`
* `filterThirdPartyResources`: Indicates if third party resources should be filtered out (Default: `false`)
-### [PageSpeed Insights Stats](./components/widgets/pagespeed-insights/stats.js)
+### [PageSpeed Insights Stats](components/widgets/pagespeed-insights/PageSpeedInsightsStats.tsx)
#### Example
@@ -305,7 +305,7 @@ import PageSpeedInsightsStats from '../components/widgets/pagespeed-insights/sta
* Acceptable values: `desktop` | `mobile`
* `filterThirdPartyResources`: Indicates if third party resources should be filtered out (Default: `false`)
-### [SonarQube](./components/widgets/sonarqube/index.js)
+### [SonarQube](components/widgets/sonarqube/SonarQube.tsx)
#### Example
@@ -326,7 +326,7 @@ import SonarQube from '../components/widgets/sonarqube'
* `componentKey`: SonarQube project key
* `authKey`: Credential key, defined in [auth.js](./auth.js)
-### [Elasticsearch Hit Count](./components/widgets/elasticsearch/hit-count.js)
+### [Elasticsearch Hit Count](components/widgets/elasticsearch/ElasticsearchHitCount.tsx)
#### Example
@@ -350,7 +350,7 @@ import ElasticsearchHitCount from '../components/widgets/elasticsearch/hit-count
* `query`: Elasticsearch query
* `authKey`: Credential key, defined in [auth.js](./auth.js)
-### [GitHub Issue Count](./components/widgets/github/issue-count.js)
+### [GitHub Issue Count](components/widgets/github/GitHubIssueCount.tsx)
#### Example
@@ -371,7 +371,7 @@ import GitHubIssueCount from '../components/widgets/github/issue-count'
* `repository`: Name of the repository
* `authKey`: Credential key, defined in [auth.js](./auth.js)
-### [Title](./components/widgets/title/index.js)
+### [Title](components/widgets/title/Title.tsx)
#### Example
@@ -383,7 +383,7 @@ import Title from '../components/widgets/title'
## Available Themes
-### [light](./styles/light-theme.js)
+### [light](styles/light-theme.ts)
#### Example
@@ -399,7 +399,7 @@ import lightTheme from '../styles/light-theme'

-### [dark](./styles/dark-theme.js)
+### [dark](styles/dark-theme.ts)
#### Example
diff --git a/auth.js b/auth.js
deleted file mode 100644
index b659852c..00000000
--- a/auth.js
+++ /dev/null
@@ -1,26 +0,0 @@
-export default {
- bitbucket: {
- username: process.env.BITBUCKET_USER,
- password: process.env.BITBUCKET_PASS
- },
- elasticsearch: {
- username: process.env.ELASTICSEARCH_USER,
- password: process.env.ELASTICSEARCH_PASS
- },
- jenkins: {
- username: process.env.JENKINS_USER,
- password: process.env.JENKINS_PASS
- },
- jira: {
- username: process.env.JIRA_USER,
- password: process.env.JIRA_PASS
- },
- sonarqube: {
- username: process.env.SONARQUBE_USER,
- password: process.env.SONARQUBE_PASS
- },
- github: {
- username: process.env.GITHUB_USER,
- password: process.env.GITHUB_PASS
- }
-}
diff --git a/auth.ts b/auth.ts
new file mode 100644
index 00000000..34f662d3
--- /dev/null
+++ b/auth.ts
@@ -0,0 +1,40 @@
+import { IAuthModel } from "./lib/auth-model";
+
+const bitbucket: IAuthModel = {
+ username: process.env.BITBUCKET_USER,
+ password: process.env.BITBUCKET_PASS,
+};
+
+const elasticsearch: IAuthModel = {
+ username: process.env.ELASTICSEARCH_USER,
+ password: process.env.ELASTICSEARCH_PASS,
+};
+
+const jenkins: IAuthModel = {
+ username: process.env.JENKINS_USER,
+ password: process.env.JENKINS_PASS,
+};
+
+const jira: IAuthModel = {
+ username: process.env.JIRA_USER,
+ password: process.env.JIRA_PASS,
+};
+
+const sonarqube: IAuthModel = {
+ username: process.env.SONARQUBE_USER,
+ password: process.env.SONARQUBE_PASS,
+};
+
+const github: IAuthModel = {
+ username: process.env.GITHUB_USER,
+ password: process.env.GITHUB_PASS,
+};
+
+export default {
+ bitbucket: bitbucket,
+ elasticsearch: elasticsearch,
+ jenkins: jenkins,
+ jira: jira,
+ sonarqube: sonarqube,
+ github: github,
+};
diff --git a/components/Badge.tsx b/components/Badge.tsx
new file mode 100644
index 00000000..f03cfb78
--- /dev/null
+++ b/components/Badge.tsx
@@ -0,0 +1,12 @@
+import styled from "styled-components";
+import { size } from "polished";
+
+export default styled.span`
+ ${size("1.75em")}
+ background-color: transparent;
+ border-radius: 50%;
+ color: ${(props) => props.theme.palette.textInvertColor};
+ display: inline-block;
+ line-height: 1.75em;
+ text-align: center;
+`;
diff --git a/components/CircleProgress.tsx b/components/CircleProgress.tsx
new file mode 100644
index 00000000..89199532
--- /dev/null
+++ b/components/CircleProgress.tsx
@@ -0,0 +1,71 @@
+import styled from "styled-components";
+import { size } from "polished";
+import { Component } from "react";
+import React from "react";
+
+const Svg = styled.svg`
+ ${size("14em")}
+ fill: transparent;
+ margin: auto;
+`;
+
+const Circle = styled.circle`
+ stroke-linecap: round;
+ stroke-width: 10;
+ transform: translate(100px, 100px) rotate(-89.9deg);
+ transition: stroke-dashoffset 0.3s linear;
+
+ &.background {
+ stroke: ${(props) => props.theme.palette.borderColor};
+ }
+
+ &.progress {
+ stroke: ${(props) => props.theme.palette.primaryColor};
+ }
+`;
+
+const Text = styled.text`
+ fill: ${(props) => props.theme.palette.textColor};
+ font-size: 4em;
+ text-anchor: middle;
+`;
+
+export interface ICircleProgressProps {
+ max: number;
+ radius: number;
+ unit: string;
+ value: number;
+}
+
+export default class CircleProgress extends Component<
+ ICircleProgressProps,
+ {}
+> {
+ static defaultProps = {
+ max: 100,
+ radius: 90,
+ unit: "",
+ };
+
+ render() {
+ const { radius, max, value, unit } = this.props;
+ const strokeDasharray = 2 * radius * Math.PI;
+ const strokeDashoffset = ((max - value) / max) * strokeDasharray;
+
+ return (
+
+ );
+ }
+}
diff --git a/components/Counter.tsx b/components/Counter.tsx
new file mode 100644
index 00000000..9f1c3cbe
--- /dev/null
+++ b/components/Counter.tsx
@@ -0,0 +1,9 @@
+import styled from "styled-components";
+import React from "react";
+
+const Counter = styled.div`
+ font-size: 4em;
+ color: ${(props) => props.theme.palette.accentColor};
+`;
+
+export default ({ value }) => {value};
diff --git a/components/Dashboard.tsx b/components/Dashboard.tsx
new file mode 100644
index 00000000..776acba1
--- /dev/null
+++ b/components/Dashboard.tsx
@@ -0,0 +1,48 @@
+import Head from "next/head";
+import styled, { createGlobalStyle, ThemeProvider } from "styled-components";
+import { normalize } from "polished";
+import React, { Component } from "react";
+
+const GlobalStyle = createGlobalStyle`
+ ${normalize()}
+
+ html {
+ font-family: 'Roboto', sans-serif;
+ }
+`;
+
+const Container = styled.main`
+ align-content: center;
+ align-items: center;
+ background-color: ${(props) => props.theme.palette.backgroundColor};
+ color: ${(props) => props.theme.palette.textColor};
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: center;
+ min-height: 100vh;
+`;
+
+export default class Dashboard extends Component<
+ { theme: any; title: string },
+ {}
+> {
+ render() {
+ const { theme, title, children } = this.props;
+ return (
+
+
+
+ {title}
+
+
+
+ {children}
+
+
+
+ );
+ }
+}
diff --git a/components/ErrorIcon.tsx b/components/ErrorIcon.tsx
new file mode 100644
index 00000000..26416062
--- /dev/null
+++ b/components/ErrorIcon.tsx
@@ -0,0 +1,15 @@
+import styled from "styled-components";
+import { size } from "polished";
+import React from "react";
+
+const Svg = styled.svg`
+ ${size("5em")}
+ fill: ${(props) => props.theme.palette.errorColor};
+`;
+
+export default () => (
+
+);
diff --git a/components/Link.tsx b/components/Link.tsx
new file mode 100644
index 00000000..309142f5
--- /dev/null
+++ b/components/Link.tsx
@@ -0,0 +1,6 @@
+import styled from "styled-components";
+
+export default styled.a`
+ text-decoration: none;
+ color: ${(props) => props.theme.palette.textColor};
+`;
diff --git a/components/LoadingIndicator.tsx b/components/LoadingIndicator.tsx
new file mode 100644
index 00000000..f0394069
--- /dev/null
+++ b/components/LoadingIndicator.tsx
@@ -0,0 +1,56 @@
+import styled, { keyframes } from "styled-components";
+import React, { Component } from "react";
+
+const rotation: string = keyframes`
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(270deg);
+ }
+`;
+
+const turn: string = keyframes`
+ 0% {
+ stroke-dashoffset: 187;
+ }
+
+ 50% {
+ stroke-dashoffset: 46.75;
+ transform: rotate(135deg);
+ }
+
+ 100% {
+ stroke-dashoffset: 187;
+ transform: rotate(450deg);
+ }
+`;
+
+const Svg = styled.svg`
+ animation: ${rotation} 1.4s linear infinite;
+ height: ${(props) => props.size};
+ width: ${(props) => props.size};
+`;
+
+const Circle = styled.circle`
+ animation: ${turn} 1.4s ease-in-out infinite;
+ fill: none;
+ stroke: ${(props) => props.theme.palette.primaryColor};
+ stroke-dasharray: 187;
+ stroke-dashoffset: 0;
+ stroke-linecap: round;
+ stroke-width: 6;
+ transform-origin: center;
+`;
+
+export default class LoadingIndicator extends Component<{ size: string }, {}> {
+ render() {
+ const svgSize = this.props.size === "small" ? "1.75em" : "5em";
+ return (
+
+ );
+ }
+}
diff --git a/components/table.js b/components/Table.tsx
similarity index 76%
rename from components/table.js
rename to components/Table.tsx
index fca7177e..cd338a9b 100644
--- a/components/table.js
+++ b/components/Table.tsx
@@ -1,13 +1,13 @@
-import styled from 'styled-components'
+import styled from "styled-components";
export default styled.table`
border-spacing: 0.75em;
-`
+`;
export const Th = styled.th`
text-align: right;
-`
+`;
export const Td = styled.td`
text-align: left;
-`
+`;
diff --git a/components/Widget.tsx b/components/Widget.tsx
new file mode 100644
index 00000000..f26e8f92
--- /dev/null
+++ b/components/Widget.tsx
@@ -0,0 +1,48 @@
+import styled from "styled-components";
+import { size } from "polished";
+import LoadingIndicator from "./LoadingIndicator";
+import ErrorIcon from "./ErrorIcon";
+import { Component } from "react";
+import React from "react";
+
+const Container = styled.div`
+ ${size("20em")}
+ align-items: center;
+ background-color: ${(props) => props.theme.palette.canvasColor};
+ border: 1px solid ${(props) => props.theme.palette.borderColor};
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin: 1em;
+ padding: 1em;
+`;
+
+const Title = styled.h1`
+ text-align: center;
+`;
+
+export default class Widget extends Component<
+ { error: boolean; loading: boolean; title: string },
+ {}
+> {
+ render() {
+ const { loading, children, error, title } = this.props;
+
+ let content;
+
+ if (loading) {
+ content = ;
+ } else if (error) {
+ content = ;
+ } else {
+ content =
{children}
;
+ }
+
+ return (
+
+ {title ? {title} : ""}
+ {content}
+
+ );
+ }
+}
diff --git a/components/badge.js b/components/badge.js
deleted file mode 100644
index 07a718d3..00000000
--- a/components/badge.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import styled from 'styled-components'
-import { size } from 'polished'
-
-export default styled.span`
- ${size('1.75em')}
- background-color: transparent;
- border-radius: 50%;
- color: ${props => props.theme.palette.textInvertColor};
- display: inline-block;
- line-height: 1.75em;
- text-align: center;
-`
diff --git a/components/circle-progress.js b/components/circle-progress.js
deleted file mode 100644
index 4a57bd4f..00000000
--- a/components/circle-progress.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import styled from 'styled-components'
-import { size } from 'polished'
-
-const Svg = styled.svg`
- ${size('14em')}
- fill: transparent;
- margin: auto;
-`
-
-const Circle = styled.circle`
- stroke-linecap: round;
- stroke-width: 10;
- transform: translate(100px, 100px) rotate(-89.9deg);
- transition: stroke-dashoffset 0.3s linear;
-
- &.background {
- stroke: ${props => props.theme.palette.borderColor};
- }
-
- &.progress {
- stroke: ${props => props.theme.palette.primaryColor};
- }
-`
-
-const Text = styled.text`
- fill: ${props => props.theme.palette.textColor};
- font-size: 4em;
- text-anchor: middle;
-`
-
-export default ({ max = 100, radius = 90, unit = '', value }) => {
- const strokeDasharray = 2 * radius * Math.PI
- const strokeDashoffset = ((max - value) / max) * strokeDasharray
-
- return (
-
- )
-}
diff --git a/components/counter.js b/components/counter.js
deleted file mode 100644
index 49f1d957..00000000
--- a/components/counter.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import styled from 'styled-components'
-
-const Counter = styled.div`
- font-size: 4em;
- color: ${props => props.theme.palette.accentColor};
-`
-
-export default ({ value }) => (
- {value}
-)
diff --git a/components/dashboard.js b/components/dashboard.js
deleted file mode 100644
index ff5d231d..00000000
--- a/components/dashboard.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import Head from 'next/head'
-import styled, { createGlobalStyle, ThemeProvider } from 'styled-components'
-import { normalize } from 'polished'
-
-const GlobalStyle = createGlobalStyle`
- ${normalize()}
-
- html {
- font-family: 'Roboto', sans-serif;
- }
-`
-
-const Container = styled.main`
- align-content: center;
- align-items: center;
- background-color: ${props => props.theme.palette.backgroundColor};
- color: ${props => props.theme.palette.textColor};
- display: flex;
- flex-flow: row wrap;
- justify-content: center;
- min-height: 100vh;
-`
-
-export default ({ children, theme, title = 'Dashboard' }) => (
-
-
-
- {title}
-
-
-
-
- {children}
-
-
-
-
-)
diff --git a/components/error-icon.js b/components/error-icon.js
deleted file mode 100644
index b9280426..00000000
--- a/components/error-icon.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import styled from 'styled-components'
-import { size } from 'polished'
-
-const Svg = styled.svg`
- ${size('5em')}
- fill: ${props => props.theme.palette.errorColor};
-`
-
-export default () => (
-
-)
diff --git a/components/link.js b/components/link.js
deleted file mode 100644
index 2892a951..00000000
--- a/components/link.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import styled from 'styled-components'
-
-export default styled.a`
- text-decoration: none;
- color: ${props => props.theme.palette.textColor};
-`
diff --git a/components/loading-indicator.js b/components/loading-indicator.js
deleted file mode 100644
index 286d46ea..00000000
--- a/components/loading-indicator.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import styled, { keyframes } from 'styled-components'
-
-const rotation = keyframes`
- 0% {
- transform: rotate(0deg);
- }
-
- 100% {
- transform: rotate(270deg);
- }
-`
-
-const turn = keyframes`
- 0% {
- stroke-dashoffset: 187;
- }
-
- 50% {
- stroke-dashoffset: 46.75;
- transform: rotate(135deg);
- }
-
- 100% {
- stroke-dashoffset: 187;
- transform: rotate(450deg);
- }
-`
-
-const Svg = styled.svg`
- animation: ${rotation} 1.4s linear infinite;
- height: ${props => props.size};
- width: ${props => props.size};
-`
-
-const Circle = styled.circle`
- animation: ${turn} 1.4s ease-in-out infinite;
- fill: none;
- stroke: ${props => props.theme.palette.primaryColor};
- stroke-dasharray: 187;
- stroke-dashoffset: 0;
- stroke-linecap: round;
- stroke-width: 6;
- transform-origin: center;
-`
-
-export default ({ size = 'medium' }) => {
- const svgSize = size === 'small' ? '1.75em' : '5em'
-
- return (
-
- )
-}
diff --git a/components/widget.js b/components/widget.js
deleted file mode 100644
index 0f23cef7..00000000
--- a/components/widget.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import styled from 'styled-components'
-import { size } from 'polished'
-import LoadingIndicator from './loading-indicator'
-import ErrorIcon from './error-icon'
-
-const Container = styled.div`
- ${size('20em')}
- align-items: center;
- background-color: ${props => props.theme.palette.canvasColor};
- border: 1px solid ${props => props.theme.palette.borderColor};
- display: flex;
- flex-direction: column;
- justify-content: center;
- margin: 1em;
- padding: 1em;
-`
-
-const Title = styled.h1`
- text-align: center;
-`
-
-export default ({ children, error = false, loading = false, title = '' }) => {
- let content
-
- if (loading) {
- content =
- } else if (error) {
- content =
- } else {
- content = {children}
- }
-
- return (
-
- {title ? {title} : ''}
- {content}
-
- )
-}
diff --git a/components/widgets/bitbucket/BitbucketPullRequestCount.tsx b/components/widgets/bitbucket/BitbucketPullRequestCount.tsx
new file mode 100644
index 00000000..eba3056d
--- /dev/null
+++ b/components/widgets/bitbucket/BitbucketPullRequestCount.tsx
@@ -0,0 +1,78 @@
+import React, { Component } from "react";
+import fetch from "isomorphic-unfetch";
+import Widget from "../../Widget";
+import Counter from "../../Counter";
+import { basicAuthHeader } from "../../../lib/auth";
+import {
+ IBitbucketPullRequestCountProps,
+ IBitbucketPullRequestCountState,
+} from "./bitbucket-model";
+
+export default class BitbucketPullRequestCount extends Component<
+ IBitbucketPullRequestCountProps,
+ IBitbucketPullRequestCountState
+> {
+ static defaultProps = {
+ interval: 1000 * 60 * 5,
+ title: "Bitbucket PR Count",
+ users: [],
+ };
+
+ state = {
+ count: 0,
+ error: false,
+ loading: true,
+ };
+
+ timeout: any = 0;
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ async fetchInformation() {
+ const { authKey, url, project, repository, users } = this.props;
+ const opts = authKey ? { headers: basicAuthHeader(authKey) } : {};
+
+ try {
+ const res = await fetch(
+ `${url}/rest/api/1.0/projects/${project}/repos/${repository}/pull-requests?limit=100`,
+ opts
+ );
+ const json = await res.json();
+
+ let count: number;
+ if (users.length) {
+ count = json.values.filter((el) => users.includes(el.user.slug)).length;
+ } else {
+ count = json.size;
+ }
+
+ this.setState({ count, error: false, loading: false });
+ } catch (error) {
+ this.setState({ error: true, loading: false });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ render() {
+ const { count, error, loading } = this.state;
+ const { title } = this.props;
+ return (
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/bitbucket/bitbucket-model.ts b/components/widgets/bitbucket/bitbucket-model.ts
new file mode 100644
index 00000000..a49dde93
--- /dev/null
+++ b/components/widgets/bitbucket/bitbucket-model.ts
@@ -0,0 +1,15 @@
+export interface IBitbucketPullRequestCountProps {
+ interval: number;
+ title: string;
+ users: Array;
+ authKey: string;
+ url: string;
+ project: string;
+ repository: string;
+}
+
+export interface IBitbucketPullRequestCountState {
+ count: number;
+ error: boolean;
+ loading: boolean;
+}
diff --git a/components/widgets/bitbucket/pull-request-count.js b/components/widgets/bitbucket/pull-request-count.js
deleted file mode 100644
index 97810186..00000000
--- a/components/widgets/bitbucket/pull-request-count.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import { Component } from 'react'
-import fetch from 'isomorphic-unfetch'
-import { object, string, number, array } from 'yup'
-import Widget from '../../widget'
-import Counter from '../../counter'
-import { basicAuthHeader } from '../../../lib/auth'
-
-const schema = object().shape({
- url: string().url().required(),
- project: string().required(),
- repository: string().required(),
- interval: number(),
- title: string(),
- users: array().of(string()),
- authKey: string()
-})
-
-export default class BitbucketPullRequestCount extends Component {
- static defaultProps = {
- interval: 1000 * 60 * 5,
- title: 'Bitbucket PR Count',
- users: []
- }
-
- state = {
- count: 0,
- error: false,
- loading: true
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- async fetchInformation () {
- const { authKey, url, project, repository, users } = this.props
- const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
-
- try {
- const res = await fetch(`${url}/rest/api/1.0/projects/${project}/repos/${repository}/pull-requests?limit=100`, opts)
- const json = await res.json()
-
- let count
- if (users.length) {
- count = json.values.filter((el) => users.includes(el.user.slug)).length
- } else {
- count = json.size
- }
-
- this.setState({ count, error: false, loading: false })
- } catch (error) {
- this.setState({ error: true, loading: false })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- render () {
- const { count, error, loading } = this.state
- const { title } = this.props
- return (
-
-
-
- )
- }
-}
diff --git a/components/widgets/datetime/DateTime.tsx b/components/widgets/datetime/DateTime.tsx
new file mode 100644
index 00000000..85a9df53
--- /dev/null
+++ b/components/widgets/datetime/DateTime.tsx
@@ -0,0 +1,54 @@
+import React, { Component } from "react";
+import tinytime from "tinytime";
+import styled from "styled-components";
+import Widget from "../../Widget";
+import { IDateTimeProps, IDateTimeState } from "./datetime-model";
+
+const TimeItem = styled.div`
+ font-size: 4em;
+ text-align: center;
+`;
+
+const DateItem = styled.div`
+ font-size: 1.5em;
+ text-align: center;
+`;
+
+export default class DateTime extends Component<
+ IDateTimeProps,
+ IDateTimeState
+> {
+ static defaultProps = {
+ interval: 1000 * 10,
+ };
+
+ state = {
+ date: new Date(),
+ };
+
+ interval: any = 0;
+
+ componentDidMount() {
+ const { interval } = this.props;
+
+ this.interval = setInterval(() => this.updateTime(), interval);
+ }
+
+ updateTime() {
+ this.setState({ date: new Date() });
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.interval);
+ }
+
+ render() {
+ const { date } = this.state;
+ return (
+
+ {tinytime("{H}:{mm}").render(date)}
+ {tinytime("{DD}.{Mo}.{YYYY}").render(date)}
+
+ );
+ }
+}
diff --git a/components/widgets/datetime/datetime-model.ts b/components/widgets/datetime/datetime-model.ts
new file mode 100644
index 00000000..b6f6d64f
--- /dev/null
+++ b/components/widgets/datetime/datetime-model.ts
@@ -0,0 +1,7 @@
+export interface IDateTimeProps {
+ interval: number;
+}
+
+export interface IDateTimeState {
+ date: Date;
+}
diff --git a/components/widgets/datetime/index.js b/components/widgets/datetime/index.js
deleted file mode 100644
index 15b49db5..00000000
--- a/components/widgets/datetime/index.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Component } from 'react'
-import tinytime from 'tinytime'
-import styled from 'styled-components'
-import Widget from '../../widget'
-
-const TimeItem = styled.div`
- font-size: 4em;
- text-align: center;
-`
-
-const DateItem = styled.div`
- font-size: 1.5em;
- text-align: center;
-`
-
-export default class DateTime extends Component {
- static defaultProps = {
- interval: 1000 * 10
- }
-
- state = {
- date: new Date()
- }
-
- componentDidMount () {
- const { interval } = this.props
-
- this.interval = setInterval(() => this.updateTime(), interval)
- }
-
- updateTime () {
- this.setState({ date: new Date() })
- }
-
- componentWillUnmount () {
- clearInterval(this.interval)
- }
-
- render () {
- const { date } = this.state
- return (
-
- {tinytime('{H}:{mm}').render(date)}
- {tinytime('{DD}.{Mo}.{YYYY}').render(date)}
-
- )
- }
-}
diff --git a/components/widgets/elasticsearch/ElasticsearchHitCount.tsx b/components/widgets/elasticsearch/ElasticsearchHitCount.tsx
new file mode 100644
index 00000000..664eb8a7
--- /dev/null
+++ b/components/widgets/elasticsearch/ElasticsearchHitCount.tsx
@@ -0,0 +1,67 @@
+import React, { Component } from "react";
+import fetch from "isomorphic-unfetch";
+import Widget from "../../Widget";
+import Counter from "../../Counter";
+import { basicAuthHeader } from "../../../lib/auth";
+import {
+ IElasticsearchHitCountProps,
+ IElasticsearchHitCountState,
+} from "./elasticsearch-model";
+
+export default class ElasticsearchHitCount extends Component<
+ IElasticsearchHitCountProps,
+ IElasticsearchHitCountState
+> {
+ static defaultProps = {
+ interval: 1000 * 60 * 5,
+ title: "Elasticsearch Hit Count",
+ };
+
+ state = {
+ count: 0,
+ error: false,
+ loading: true,
+ };
+
+ timeout: any = 0;
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ async fetchInformation() {
+ const { authKey, index, query, url } = this.props;
+ const opts = authKey ? { headers: basicAuthHeader(authKey) } : {};
+
+ try {
+ const res = await fetch(`${url}/${index}/_search?q=${query}`, opts);
+ const json = await res.json();
+
+ this.setState({ count: json.hits.total, error: false, loading: false });
+ } catch (error) {
+ this.setState({ error: true, loading: false });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ render() {
+ const { count, error, loading } = this.state;
+ const { title } = this.props;
+ return (
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/elasticsearch/elasticsearch-model.ts b/components/widgets/elasticsearch/elasticsearch-model.ts
new file mode 100644
index 00000000..34480ae8
--- /dev/null
+++ b/components/widgets/elasticsearch/elasticsearch-model.ts
@@ -0,0 +1,14 @@
+export interface IElasticsearchHitCountProps {
+ interval: number;
+ title: string;
+ authKey: string;
+ index: string;
+ query: string;
+ url: string;
+}
+
+export interface IElasticsearchHitCountState {
+ count: number;
+ error: boolean;
+ loading: boolean;
+}
diff --git a/components/widgets/elasticsearch/hit-count.js b/components/widgets/elasticsearch/hit-count.js
deleted file mode 100644
index dbce320d..00000000
--- a/components/widgets/elasticsearch/hit-count.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { Component } from 'react'
-import fetch from 'isomorphic-unfetch'
-import { object, string, number } from 'yup'
-import Widget from '../../widget'
-import Counter from '../../counter'
-import { basicAuthHeader } from '../../../lib/auth'
-
-const schema = object().shape({
- url: string().url().required(),
- index: string().required(),
- query: string().required(),
- interval: number(),
- title: string()
-})
-
-export default class ElasticsearchHitCount extends Component {
- static defaultProps = {
- interval: 1000 * 60 * 5,
- title: 'Elasticsearch Hit Count'
- }
-
- state = {
- count: 0,
- error: false,
- loading: true
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- async fetchInformation () {
- const { authKey, index, query, url } = this.props
- const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
-
- try {
- const res = await fetch(`${url}/${index}/_search?q=${query}`, opts)
- const json = await res.json()
-
- this.setState({ count: json.hits.total, error: false, loading: false })
- } catch (error) {
- this.setState({ error: true, loading: false })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- render () {
- const { count, error, loading } = this.state
- const { title } = this.props
- return (
-
-
-
- )
- }
-}
diff --git a/components/widgets/github/GitHubIssueCount.tsx b/components/widgets/github/GitHubIssueCount.tsx
new file mode 100644
index 00000000..77082efa
--- /dev/null
+++ b/components/widgets/github/GitHubIssueCount.tsx
@@ -0,0 +1,72 @@
+import React, { Component } from "react";
+import fetch from "isomorphic-unfetch";
+import Widget from "../../Widget";
+import Counter from "../../Counter";
+import { basicAuthHeader } from "../../../lib/auth";
+import { IGitHubIssueCountProps, IGitHubIssueCountState } from "./github-model";
+
+export default class GitHubIssueCount extends Component<
+ IGitHubIssueCountProps,
+ IGitHubIssueCountState
+> {
+ static defaultProps = {
+ interval: 1000 * 60 * 5,
+ title: "GitHub Issue Count",
+ };
+
+ state = {
+ count: 0,
+ error: false,
+ loading: true,
+ };
+
+ timeout: any = 0;
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ async fetchInformation() {
+ const { authKey, owner, repository } = this.props;
+ const opts =
+ authKey && authKey != "" ? { headers: basicAuthHeader(authKey) } : {};
+
+ try {
+ const res = await fetch(
+ `https://api.github.com/repos/${owner}/${repository}`,
+ opts
+ );
+ const json = await res.json();
+
+ this.setState({
+ count: json.open_issues_count,
+ error: false,
+ loading: false,
+ });
+ } catch (error) {
+ this.setState({ error: true, loading: false });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ render() {
+ const { count, error, loading } = this.state;
+ const { title } = this.props;
+ return (
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/github/github-model.ts b/components/widgets/github/github-model.ts
new file mode 100644
index 00000000..40cd04a9
--- /dev/null
+++ b/components/widgets/github/github-model.ts
@@ -0,0 +1,13 @@
+export interface IGitHubIssueCountProps {
+ interval: number;
+ title: string;
+ authKey: string;
+ owner: string;
+ repository: string;
+}
+
+export interface IGitHubIssueCountState {
+ count: number;
+ error: boolean;
+ loading: boolean;
+}
diff --git a/components/widgets/github/issue-count.js b/components/widgets/github/issue-count.js
deleted file mode 100644
index 3594ddad..00000000
--- a/components/widgets/github/issue-count.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { Component } from 'react'
-import fetch from 'isomorphic-unfetch'
-import { object, string, number } from 'yup'
-import Widget from '../../widget'
-import Counter from '../../counter'
-import { basicAuthHeader } from '../../../lib/auth'
-
-const schema = object().shape({
- owner: string().required(),
- repository: string().required(),
- interval: number(),
- title: string(),
- authKey: string()
-})
-
-export default class GitHubIssueCount extends Component {
- static defaultProps = {
- interval: 1000 * 60 * 5,
- title: 'GitHub Issue Count'
- }
-
- state = {
- count: 0,
- error: false,
- loading: true
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- async fetchInformation () {
- const { authKey, owner, repository } = 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()
-
- this.setState({ count: json.open_issues_count, error: false, loading: false })
- } catch (error) {
- this.setState({ error: true, loading: false })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- render () {
- const { count, error, loading } = this.state
- const { title } = this.props
- return (
-
-
-
- )
- }
-}
diff --git a/components/widgets/jenkins/JenkinsBuildBuration.tsx b/components/widgets/jenkins/JenkinsBuildBuration.tsx
new file mode 100644
index 00000000..1bc1ec9e
--- /dev/null
+++ b/components/widgets/jenkins/JenkinsBuildBuration.tsx
@@ -0,0 +1,137 @@
+import React, { Component } from "react";
+import fetch from "isomorphic-unfetch";
+import styled from "styled-components";
+
+import Widget from "../../Widget";
+import Link from "../../Link";
+import Table, { Td, Th } from "../../Table";
+import LoadingIndicator from "../../LoadingIndicator";
+import { basicAuthHeader } from "../../../lib/auth";
+import {
+ IJenkinsBuild,
+ IJenkinsBuildBurationProps,
+ IJenkinsBuildBurationState,
+} from "./jenkins-model";
+
+const Kpi = styled.span`
+ color: ${(props) => props.theme.palette.primaryColor};
+ font-weight: 700;
+ font-size: 20px;
+`;
+
+export default class JenkinsBuildDuration extends Component<
+ IJenkinsBuildBurationProps,
+ IJenkinsBuildBurationState
+> {
+ static defaultProps = {
+ interval: 1000 * 60 * 5,
+ title: "Build Duration",
+ };
+
+ state = {
+ loading: true,
+ error: false,
+ builds: [],
+ };
+
+ timeout: any = 0;
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ formatTime(ms: number) {
+ const s = ms / 1000;
+
+ if (s > 60) {
+ const min: number = Math.floor(s / 60);
+ let minSec: number = Math.round(s - min * 60);
+ const minSecString: string =
+ minSec.toString().length === 1 ? `0${minSec}` : minSec.toString();
+
+ return (
+ <>
+
+ {min}:{minSecString}
+ {" "}
+ min
+ >
+ );
+ }
+
+ return (
+ <>
+ {Math.round(s)} sec
+ >
+ );
+ }
+
+ async fetchInformation() {
+ const { authKey, jobs, url } = this.props;
+ const opts = authKey ? { headers: basicAuthHeader(authKey) } : {};
+
+ try {
+ const builds: Array = await Promise.all(
+ jobs.map(async (job) => {
+ const branch = job.branch ? `job/${job.branch}/` : "";
+ const res = await fetch(
+ `${url}/job/${job.path}/${branch}lastBuild/api/json`,
+ opts
+ );
+ const json = await res.json();
+
+ return {
+ name: job.label,
+ url: json.url,
+ duration: json.duration,
+ };
+ })
+ );
+
+ this.setState({ error: false, loading: false, builds });
+ } catch (error) {
+ this.setState({ error: true, loading: false });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ render() {
+ const { loading, error, builds } = this.state;
+ const { title } = this.props;
+
+ return (
+
+
+
+ {builds &&
+ builds.map((build: IJenkinsBuild) => (
+
+ {build.name} |
+
+
+ {build.duration ? (
+ this.formatTime(build.duration)
+ ) : (
+
+ )}
+
+ |
+
+ ))}
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/jenkins/JenkinsJobHealth.tsx b/components/widgets/jenkins/JenkinsJobHealth.tsx
new file mode 100644
index 00000000..7dedb7b9
--- /dev/null
+++ b/components/widgets/jenkins/JenkinsJobHealth.tsx
@@ -0,0 +1,125 @@
+import React, { Component } from "react";
+import fetch from "isomorphic-unfetch";
+import styled from "styled-components";
+
+import Widget from "../../Widget";
+import Link from "../../Link";
+import Table, { Td, Th } from "../../Table";
+import LoadingIndicator from "../../LoadingIndicator";
+import { basicAuthHeader } from "../../../lib/auth";
+import {
+ IJenkinsBuild,
+ IJenkinsJobHealthProps,
+ IJenkinsJobHealthState,
+} from "./jenkins-model";
+
+const jenkinsKpiColor = ({ theme, value }) => {
+ if (value < 70) return theme.palette.errorColor;
+ if (value >= 70 && value < 90) return theme.palette.warnColor;
+ return theme.palette.successColor;
+};
+
+const Kpi = styled.span`
+ color: ${jenkinsKpiColor};
+ font-weight: 700;
+ font-size: 20px;
+`;
+
+export default class JenkinsJobHealth extends Component<
+ IJenkinsJobHealthProps,
+ IJenkinsJobHealthState
+> {
+ static defaultProps = {
+ interval: 1000 * 60 * 5,
+ title: "Job Health",
+ };
+
+ state = {
+ loading: true,
+ error: false,
+ builds: [],
+ };
+
+ timeout: any = 0;
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ async fetchInformation() {
+ const { authKey, jobs, url } = this.props;
+ const opts = authKey ? { headers: basicAuthHeader(authKey) } : {};
+
+ try {
+ const builds: Array = await Promise.all(
+ jobs.map(async (job) => {
+ const branch = job.branch ? `job/${job.branch}/` : "";
+ const res = await fetch(
+ `${url}/job/${job.path}/${branch}api/json`,
+ opts
+ );
+ const json = await res.json();
+
+ return {
+ name: job.label,
+ url: json.url,
+ health: json.healthReport,
+ duration: 0,
+ };
+ })
+ );
+
+ this.setState({ error: false, loading: false, builds });
+ } catch (error) {
+ this.setState({ error: true, loading: false });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ renderHealth(build) {
+ return build.map((b, index, array) => (
+
+ {b.score}
+ {index < array.length - 1 && / }
+
+ ));
+ }
+
+ render() {
+ const { loading, error, builds } = this.state;
+ const { title } = this.props;
+
+ return (
+
+
+
+ {builds &&
+ builds.map((build) => (
+
+ {build.name} |
+
+ {build.health ? (
+ this.renderHealth(build.health)
+ ) : (
+
+ )}
+ |
+
+ ))}
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/jenkins/JenkinsJobStatus.tsx b/components/widgets/jenkins/JenkinsJobStatus.tsx
new file mode 100644
index 00000000..e8b868bc
--- /dev/null
+++ b/components/widgets/jenkins/JenkinsJobStatus.tsx
@@ -0,0 +1,126 @@
+import React, { Component } from "react";
+import fetch from "isomorphic-unfetch";
+import styled from "styled-components";
+import Widget from "../../Widget";
+import Table, { Td, Th } from "../../Table";
+import Badge from "../../Badge";
+import LoadingIndicator from "../../LoadingIndicator";
+import { basicAuthHeader } from "../../../lib/auth";
+import {
+ IJenkinsBuild,
+ IJenkinsJobStatusProps,
+ IJenkinsJobStatusState,
+} from "./jenkins-model";
+
+const jenkinsBadgeColor = ({ theme, status }) => {
+ switch (status) {
+ case "FAILURE":
+ return theme.palette.errorColor;
+ case "UNSTABLE":
+ return theme.palette.warnColor;
+ case "SUCCESS":
+ return theme.palette.successColor;
+ case "ABORTED":
+ case "NOT_BUILT":
+ return theme.palette.disabledColor;
+ default:
+ // null = 'In Progress'
+ return "transparent";
+ }
+};
+
+const JenkinsBadge = styled(Badge)`
+ background-color: ${jenkinsBadgeColor};
+`;
+
+export default class JenkinsJobStatus extends Component<
+ IJenkinsJobStatusProps,
+ IJenkinsJobStatusState
+> {
+ static defaultProps = {
+ interval: 1000 * 60 * 5,
+ title: "Job Status",
+ };
+
+ state = {
+ loading: true,
+ error: false,
+ builds: [],
+ };
+
+ timeout: any = 0;
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ async fetchInformation() {
+ const { authKey, jobs, url } = this.props;
+ const opts = authKey ? { headers: basicAuthHeader(authKey) } : {};
+
+ try {
+ const builds: Array = await Promise.all(
+ jobs.map(async (job) => {
+ const branch = job.branch ? `job/${job.branch}/` : "";
+ const res = await fetch(
+ `${url}/job/${job.path}/${branch}lastBuild/api/json`,
+ opts
+ );
+ const json = await res.json();
+
+ return {
+ name: job.label,
+ url: json.url,
+ result: json.result,
+ duration: 0,
+ };
+ })
+ );
+
+ this.setState({ error: false, loading: false, builds });
+ } catch (error) {
+ this.setState({ error: true, loading: false });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ render() {
+ const { loading, error, builds } = this.state;
+ const { title } = this.props;
+
+ return (
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/jenkins/build-duration.js b/components/widgets/jenkins/build-duration.js
deleted file mode 100644
index 9c1465d4..00000000
--- a/components/widgets/jenkins/build-duration.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import { Component } from 'react'
-import fetch from 'isomorphic-unfetch'
-import styled from 'styled-components'
-import { object, string, array, number } from 'yup'
-
-import Widget from '../../widget'
-import Link from '../../link'
-import Table, { Th, Td } from '../../table'
-import LoadingIndicator from '../../loading-indicator'
-import { basicAuthHeader } from '../../../lib/auth'
-
-const Kpi = styled.span`
- color: ${props => props.theme.palette.primaryColor};
- font-weight: 700;
- font-size: 20px;
-`
-
-const schema = object().shape({
- url: string().url().required(),
- jobs: array(object({
- label: string().required(),
- path: string().required(),
- branch: string()
- })).required(),
- interval: number(),
- title: string(),
- authKey: string()
-})
-
-export default class JenkinsBuildDuration extends Component {
- static defaultProps = {
- interval: 1000 * 60 * 5,
- title: 'Build Duration'
- }
-
- state = {
- loading: true,
- error: false
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- formatTime (ms) {
- const s = ms / 1000
-
- if (s > 60) {
- const min = Math.floor(s / 60)
- let minSec = Math.round(s - (min * 60))
- minSec = minSec.toString().length === 1 ? `0${minSec}` : minSec
-
- return <>{min}:{minSec} min>
- }
-
- return <>{Math.round(s)} sec>
- }
-
- async fetchInformation () {
- const { authKey, jobs, url } = this.props
- const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
-
- try {
- const builds = await Promise.all(
- jobs.map(async job => {
- const branch = job.branch ? `job/${job.branch}/` : ''
- const res = await fetch(`${url}/job/${job.path}/${branch}lastBuild/api/json`, opts)
- const json = await res.json()
-
- return {
- name: job.label,
- url: json.url,
- duration: json.duration
- }
- })
- )
-
- this.setState({ error: false, loading: false, builds })
- } catch (error) {
- this.setState({ error: true, loading: false })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- render () {
- const { loading, error, builds } = this.state
- const { title } = this.props
-
- return (
-
-
-
- {builds && builds.map(build => (
-
- {build.name} |
-
-
- {
- build.duration
- ? this.formatTime(build.duration)
- :
- }
-
- |
-
- ))}
-
-
-
- )
- }
-}
diff --git a/components/widgets/jenkins/jenkins-model.ts b/components/widgets/jenkins/jenkins-model.ts
new file mode 100644
index 00000000..6726f260
--- /dev/null
+++ b/components/widgets/jenkins/jenkins-model.ts
@@ -0,0 +1,53 @@
+export interface IJenkinsJob {
+ label: string;
+ path: string;
+ branch: string;
+}
+
+export interface IJenkinsBuild {
+ name: string;
+ url: string;
+ duration: number;
+}
+
+export interface IJenkinsBuildBurationProps {
+ url: string;
+ jobs: Array;
+ interval: number;
+ title: string;
+ authKey: string;
+}
+
+export interface IJenkinsBuildBurationState {
+ loading: boolean;
+ error: boolean;
+ builds: Array;
+}
+
+export interface IJenkinsJobHealthProps {
+ url: string;
+ jobs: Array;
+ interval: number;
+ title: string;
+ authKey: string;
+}
+
+export interface IJenkinsJobHealthState {
+ loading: boolean;
+ error: boolean;
+ builds: Array;
+}
+
+export interface IJenkinsJobStatusProps {
+ url: string;
+ jobs: Array;
+ interval: number;
+ title: string;
+ authKey: string;
+}
+
+export interface IJenkinsJobStatusState {
+ loading: boolean;
+ error: boolean;
+ builds: Array;
+}
diff --git a/components/widgets/jenkins/job-health.js b/components/widgets/jenkins/job-health.js
deleted file mode 100644
index f75c177d..00000000
--- a/components/widgets/jenkins/job-health.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import { Component } from 'react'
-import fetch from 'isomorphic-unfetch'
-import styled from 'styled-components'
-import { object, string, array, number } from 'yup'
-
-import Widget from '../../widget'
-import Link from '../../link'
-import Table, { Th, Td } from '../../table'
-import LoadingIndicator from '../../loading-indicator'
-import { basicAuthHeader } from '../../../lib/auth'
-
-const jenkinsKpiColor = ({ theme, value }) => {
- if (value < 70) return theme.palette.errorColor
- if (value >= 70 && value < 90) return theme.palette.warnColor
- return theme.palette.successColor
-}
-
-const Kpi = styled.span`
- color: ${jenkinsKpiColor};
- font-weight: 700;
- font-size: 20px;
-`
-
-const schema = object().shape({
- url: string().url().required(),
- jobs: array(object({
- label: string().required(),
- path: string().required(),
- branch: string()
- })).required(),
- interval: number(),
- title: string(),
- authKey: string()
-})
-
-export default class JenkinsJobHealth extends Component {
- static defaultProps = {
- interval: 1000 * 60 * 5,
- title: 'Job Health'
- }
-
- state = {
- loading: true,
- error: false
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- async fetchInformation () {
- const { authKey, jobs, url } = this.props
- const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
-
- try {
- const builds = await Promise.all(
- jobs.map(async job => {
- const branch = job.branch ? `job/${job.branch}/` : ''
- const res = await fetch(`${url}/job/${job.path}/${branch}api/json`, opts)
- const json = await res.json()
-
- return {
- name: job.label,
- url: json.url,
- health: json.healthReport
- }
- })
- )
-
- this.setState({ error: false, loading: false, builds })
- } catch (error) {
- this.setState({ error: true, loading: false })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- renderHealth (build) {
- return build.map((b, index, array) => (
-
- {b.score}
- {index < array.length - 1 && / }
-
- ))
- }
-
- render () {
- const { loading, error, builds } = this.state
- const { title } = this.props
-
- return (
-
-
-
- {builds && builds.map(build => (
-
- {build.name} |
-
- {
- build.health
- ? this.renderHealth(build.health)
- :
- }
- |
-
- ))}
-
-
-
- )
- }
-}
diff --git a/components/widgets/jenkins/job-status.js b/components/widgets/jenkins/job-status.js
deleted file mode 100644
index 03f0d04c..00000000
--- a/components/widgets/jenkins/job-status.js
+++ /dev/null
@@ -1,120 +0,0 @@
-import { Component } from 'react'
-import fetch from 'isomorphic-unfetch'
-import styled from 'styled-components'
-import { object, string, array, number } from 'yup'
-import Widget from '../../widget'
-import Table, { Th, Td } from '../../table'
-import Badge from '../../badge'
-import LoadingIndicator from '../../loading-indicator'
-import { basicAuthHeader } from '../../../lib/auth'
-
-const jenkinsBadgeColor = ({ theme, status }) => {
- switch (status) {
- case 'FAILURE':
- return theme.palette.errorColor
- case 'UNSTABLE':
- return theme.palette.warnColor
- case 'SUCCESS':
- return theme.palette.successColor
- case 'ABORTED':
- case 'NOT_BUILT':
- return theme.palette.disabledColor
- default: // null = 'In Progress'
- return 'transparent'
- }
-}
-const JenkinsBadge = styled(Badge)`
- background-color: ${jenkinsBadgeColor};
-`
-
-const schema = object().shape({
- url: string().url().required(),
- jobs: array(object({
- label: string().required(),
- path: string().required(),
- branch: string()
- })).required(),
- interval: number(),
- title: string(),
- authKey: string()
-})
-
-export default class JenkinsJobStatus extends Component {
- static defaultProps = {
- interval: 1000 * 60 * 5,
- title: 'Job Status'
- }
-
- state = {
- loading: true,
- error: false
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- async fetchInformation () {
- const { authKey, jobs, url } = this.props
- const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
-
- try {
- const builds = await Promise.all(
- jobs.map(async job => {
- const branch = job.branch ? `job/${job.branch}/` : ''
- const res = await fetch(`${url}/job/${job.path}/${branch}lastBuild/api/json`, opts)
- const json = await res.json()
-
- return {
- name: job.label,
- url: json.url,
- result: json.result
- }
- })
- )
-
- this.setState({ error: false, loading: false, builds })
- } catch (error) {
- this.setState({ error: true, loading: false })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- render () {
- const { loading, error, builds } = this.state
- const { title } = this.props
-
- return (
-
-
-
- )
- }
-}
diff --git a/components/widgets/jira/JiraIssueCount.tsx b/components/widgets/jira/JiraIssueCount.tsx
new file mode 100644
index 00000000..3069ae7d
--- /dev/null
+++ b/components/widgets/jira/JiraIssueCount.tsx
@@ -0,0 +1,64 @@
+import React, { Component } from "react";
+import fetch from "isomorphic-unfetch";
+import Widget from "../../Widget";
+import Counter from "../../Counter";
+import { basicAuthHeader } from "../../../lib/auth";
+import { IJiraIssueCountProps, IJiraIssueCountState } from "./jira-model";
+
+export default class JiraIssueCount extends Component<
+ IJiraIssueCountProps,
+ IJiraIssueCountState
+> {
+ static defaultProps = {
+ interval: 1000 * 60 * 5,
+ title: "JIRA Issue Count",
+ };
+
+ state = {
+ count: 0,
+ error: false,
+ loading: true,
+ };
+
+ timeout: any = 0;
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ async fetchInformation() {
+ const { authKey, url, query } = 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();
+
+ this.setState({ count: json.total, error: false, loading: false });
+ } catch (error) {
+ this.setState({ error: true, loading: false });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ render() {
+ const { count, error, loading } = this.state;
+ const { title } = this.props;
+ return (
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/jira/JiraSprintDaysRemaining.tsx b/components/widgets/jira/JiraSprintDaysRemaining.tsx
new file mode 100644
index 00000000..5337032e
--- /dev/null
+++ b/components/widgets/jira/JiraSprintDaysRemaining.tsx
@@ -0,0 +1,80 @@
+import React, { Component } from "react";
+import fetch from "isomorphic-unfetch";
+import Widget from "../../Widget";
+import Counter from "../../Counter";
+import { basicAuthHeader } from "../../../lib/auth";
+import {
+ IJiraSprintDaysRemainingProps,
+ IJiraSprintDaysRemainingState,
+} from "./jira-model";
+
+export default class JiraSprintDaysRemaining extends Component<
+ IJiraSprintDaysRemainingProps,
+ IJiraSprintDaysRemainingState
+> {
+ static defaultProps = {
+ interval: 1000 * 60 * 60,
+ title: "JIRA Sprint Days Remaining",
+ };
+
+ state = {
+ days: 0,
+ error: false,
+ loading: true,
+ };
+
+ timeout: any = 0;
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ calculateDays(date) {
+ const currentDate = new Date();
+ const endDate = new Date(date);
+ const timeDiff = endDate.getTime() - currentDate.getTime();
+ const diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24));
+
+ return diffDays < 0 ? 0 : diffDays;
+ }
+
+ async fetchInformation() {
+ const { authKey, boardId, url } = this.props;
+ const opts = authKey ? { headers: basicAuthHeader(authKey) } : {};
+
+ try {
+ const res = await fetch(
+ `${url}/rest/agile/1.0/board/${boardId}/sprint?state=active`,
+ opts
+ );
+ 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 });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ render() {
+ const { days, error, loading } = this.state;
+ const { title } = this.props;
+ return (
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/jira/issue-count.js b/components/widgets/jira/issue-count.js
deleted file mode 100644
index e4335528..00000000
--- a/components/widgets/jira/issue-count.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { Component } from 'react'
-import fetch from 'isomorphic-unfetch'
-import { object, string, number } from 'yup'
-import Widget from '../../widget'
-import Counter from '../../counter'
-import { basicAuthHeader } from '../../../lib/auth'
-
-const schema = object().shape({
- url: string().url().required(),
- query: string().required(),
- interval: number(),
- title: string(),
- authKey: string()
-})
-
-export default class JiraIssueCount extends Component {
- static defaultProps = {
- interval: 1000 * 60 * 5,
- title: 'JIRA Issue Count'
- }
-
- state = {
- count: 0,
- error: false,
- loading: true
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- async fetchInformation () {
- const { authKey, url, query } = 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()
-
- this.setState({ count: json.total, error: false, loading: false })
- } catch (error) {
- this.setState({ error: true, loading: false })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- render () {
- const { count, error, loading } = this.state
- const { title } = this.props
- return (
-
-
-
- )
- }
-}
diff --git a/components/widgets/jira/jira-model.ts b/components/widgets/jira/jira-model.ts
new file mode 100644
index 00000000..454020a8
--- /dev/null
+++ b/components/widgets/jira/jira-model.ts
@@ -0,0 +1,27 @@
+export interface IJiraIssueCountProps {
+ url: string;
+ query: string;
+ interval: number;
+ title: string;
+ authKey: string;
+}
+
+export interface IJiraIssueCountState {
+ count: number;
+ error: boolean;
+ loading: boolean;
+}
+
+export interface IJiraSprintDaysRemainingProps {
+ url: string;
+ boardId: number;
+ interval: number;
+ title: string;
+ authKey: string;
+}
+
+export interface IJiraSprintDaysRemainingState {
+ days: number;
+ error: boolean;
+ loading: boolean;
+}
diff --git a/components/widgets/jira/sprint-days-remaining.js b/components/widgets/jira/sprint-days-remaining.js
deleted file mode 100644
index a0feb217..00000000
--- a/components/widgets/jira/sprint-days-remaining.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import { Component } from 'react'
-import fetch from 'isomorphic-unfetch'
-import { object, string, number } from 'yup'
-import Widget from '../../widget'
-import Counter from '../../counter'
-import { basicAuthHeader } from '../../../lib/auth'
-
-const schema = object().shape({
- url: string().url().required(),
- boardId: number().required(),
- interval: number(),
- title: string(),
- authKey: string()
-})
-
-export default class JiraSprintDaysRemaining extends Component {
- static defaultProps = {
- interval: 1000 * 60 * 60,
- title: 'JIRA Sprint Days Remaining'
- }
-
- state = {
- days: 0,
- error: false,
- loading: true
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- calculateDays (date) {
- const currentDate = new Date()
- const endDate = new Date(date)
- const timeDiff = endDate.getTime() - currentDate.getTime()
- const diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24))
-
- return diffDays < 0 ? 0 : diffDays
- }
-
- async fetchInformation () {
- const { authKey, boardId, url } = this.props
- const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
-
- try {
- const res = await fetch(`${url}/rest/agile/1.0/board/${boardId}/sprint?state=active`, opts)
- 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 })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- render () {
- const { days, error, loading } = this.state
- const { title } = this.props
- return (
-
-
-
- )
- }
-}
diff --git a/components/widgets/pagespeed-insights/PageSpeedInsideScore.tsx b/components/widgets/pagespeed-insights/PageSpeedInsideScore.tsx
new file mode 100644
index 00000000..f66c9d98
--- /dev/null
+++ b/components/widgets/pagespeed-insights/PageSpeedInsideScore.tsx
@@ -0,0 +1,79 @@
+import React, { Component } from "react";
+import fetch from "isomorphic-unfetch";
+import CircleProgress from "../../CircleProgress";
+import Widget from "../../Widget";
+import {
+ IPageSpeedInsideScoreProps,
+ IPageSpeedInsideScoreState,
+} from "./pagespeed-model";
+
+export default class PageSpeedInsightsScore extends Component<
+ IPageSpeedInsideScoreProps,
+ IPageSpeedInsideScoreState
+> {
+ static defaultProps = {
+ filterThirdPartyResources: false,
+ interval: 1000 * 60 * 60 * 12,
+ strategy: "desktop",
+ title: "PageSpeed Score",
+ };
+
+ state = {
+ score: 0,
+ loading: true,
+ error: false,
+ };
+
+ timeout: any = 0;
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ async fetchInformation() {
+ const { url, filterThirdPartyResources, strategy } = this.props;
+
+ const searchParams = [
+ `url=${url}`,
+ `filter_third_party_resources=${filterThirdPartyResources}`,
+ `strategy=${strategy}`,
+ ].join("&");
+
+ try {
+ const res = await fetch(
+ `https://www.googleapis.com/pagespeedonline/v2/runPagespeed?${searchParams}`
+ );
+ const json = await res.json();
+
+ this.setState({
+ error: false,
+ loading: false,
+ score: json.ruleGroups.SPEED.score,
+ });
+ } catch (error) {
+ this.setState({ error: true, loading: false });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ render() {
+ const { error, loading, score } = this.state;
+ const { title } = this.props;
+ return (
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/pagespeed-insights/PageSpeedInsightsStats.tsx b/components/widgets/pagespeed-insights/PageSpeedInsightsStats.tsx
new file mode 100644
index 00000000..34ac6cbb
--- /dev/null
+++ b/components/widgets/pagespeed-insights/PageSpeedInsightsStats.tsx
@@ -0,0 +1,153 @@
+import React, { Component } from "react";
+import fetch from "isomorphic-unfetch";
+import Table, { Td, Th } from "../../Table";
+import Widget from "../../Widget";
+import {
+ IPageSpeedInsightsStatsProps,
+ IPageSpeedInsightsStatsState,
+ IPageSpeedStats,
+} from "./pagespeed-model";
+
+export default class PageSpeedInsightsStats extends Component<
+ IPageSpeedInsightsStatsProps,
+ IPageSpeedInsightsStatsState
+> {
+ static defaultProps = {
+ filterThirdPartyResources: false,
+ interval: 1000 * 60 * 60 * 12,
+ strategy: "desktop",
+ title: "PageSpeed Stats",
+ };
+
+ state = {
+ stats: {
+ requestSize: 0,
+ requestCount: 0,
+ cssCount: 0,
+ cssSize: 0,
+ htmlSize: 0,
+ imageSize: 0,
+ javascriptCount: 0,
+ javascriptSize: 0,
+ otherSize: 0,
+ },
+ loading: true,
+ error: false,
+ };
+
+ timeout: any = 0;
+
+ static bytesToKilobytes(bytes: number): number {
+ return bytes > 0 ? Number.parseFloat((bytes / 1024).toFixed(1)) : 0;
+ }
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ async fetchInformation() {
+ const { url, filterThirdPartyResources, strategy } = this.props;
+
+ const searchParams = [
+ `url=${url}`,
+ `filter_third_party_resources=${filterThirdPartyResources}`,
+ `strategy=${strategy}`,
+ ].join("&");
+
+ try {
+ const res = await fetch(
+ `https://www.googleapis.com/pagespeedonline/v2/runPagespeed?${searchParams}`
+ );
+ const json = await res.json();
+
+ const pageStats = json.pageStats;
+ const stats: IPageSpeedStats = {
+ cssCount: pageStats.numberCssResources || 0,
+ cssSize: PageSpeedInsightsStats.bytesToKilobytes(
+ pageStats.cssResponseBytes
+ ),
+ htmlSize: PageSpeedInsightsStats.bytesToKilobytes(
+ pageStats.htmlResponseBytes
+ ),
+ imageSize: PageSpeedInsightsStats.bytesToKilobytes(
+ pageStats.imageResponseBytes
+ ),
+ javascriptCount: pageStats.numberJsResources || 0,
+ javascriptSize: PageSpeedInsightsStats.bytesToKilobytes(
+ pageStats.javascriptResponseBytes
+ ),
+ requestCount: pageStats.numberResources || 0,
+ requestSize: PageSpeedInsightsStats.bytesToKilobytes(
+ pageStats.totalRequestBytes
+ ),
+ otherSize: PageSpeedInsightsStats.bytesToKilobytes(
+ pageStats.otherResponseBytes
+ ),
+ };
+
+ this.setState({ error: false, loading: false, stats });
+ } catch (error) {
+ this.setState({ error: true, loading: false });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ render() {
+ const { error, loading, stats } = this.state;
+ const { title } = this.props;
+ return (
+
+
+
+
+ Request |
+
+ {stats.requestSize} KB ({stats.requestCount})
+ |
+
+
+
+ JavaScript |
+
+ {stats.javascriptSize} KB ({stats.javascriptCount})
+ |
+
+
+
+ CSS |
+
+ {stats.cssSize} KB ({stats.cssCount})
+ |
+
+
+
+ HTML |
+ {stats.htmlSize} KB |
+
+
+
+ Image |
+ {stats.imageSize} KB |
+
+
+
+ Other |
+ {stats.otherSize} KB |
+
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/pagespeed-insights/pagespeed-model.ts b/components/widgets/pagespeed-insights/pagespeed-model.ts
new file mode 100644
index 00000000..a6a87e1e
--- /dev/null
+++ b/components/widgets/pagespeed-insights/pagespeed-model.ts
@@ -0,0 +1,39 @@
+export interface IPageSpeedInsideScoreProps {
+ url: string;
+ filterThirdPartyResources: boolean;
+ interval: number;
+ strategy: string;
+ title: string;
+}
+
+export interface IPageSpeedInsideScoreState {
+ score: number;
+ loading: boolean;
+ error: boolean;
+}
+
+export interface IPageSpeedInsightsStatsProps {
+ url: string;
+ filterThirdPartyResources: boolean;
+ interval: number;
+ strategy: string;
+ title: string;
+}
+
+export interface IPageSpeedInsightsStatsState {
+ stats: IPageSpeedStats;
+ loading: boolean;
+ error: boolean;
+}
+
+export interface IPageSpeedStats {
+ requestSize: number;
+ requestCount: number;
+ cssCount: number;
+ cssSize: number;
+ htmlSize: number;
+ imageSize: number;
+ javascriptCount: number;
+ javascriptSize: number;
+ otherSize: number;
+}
diff --git a/components/widgets/pagespeed-insights/score.js b/components/widgets/pagespeed-insights/score.js
deleted file mode 100644
index 47a8ca6d..00000000
--- a/components/widgets/pagespeed-insights/score.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import { Component } from 'react'
-import fetch from 'isomorphic-unfetch'
-import { object, string, number, boolean } from 'yup'
-import CircleProgress from '../../circle-progress'
-import Widget from '../../widget'
-
-const schema = object().shape({
- url: string().url().required(),
- filterThirdPartyResources: boolean(),
- interval: number(),
- strategy: string(),
- title: string()
-})
-
-export default class PageSpeedInsightsScore extends Component {
- static defaultProps = {
- filterThirdPartyResources: false,
- interval: 1000 * 60 * 60 * 12,
- strategy: 'desktop',
- title: 'PageSpeed Score'
- }
-
- state = {
- score: 0,
- loading: true,
- error: false
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- async fetchInformation () {
- const { url, filterThirdPartyResources, strategy } = this.props
-
- const searchParams = [
- `url=${url}`,
- `filter_third_party_resources=${filterThirdPartyResources}`,
- `strategy=${strategy}`
- ].join('&')
-
- try {
- const res = await fetch(`https://www.googleapis.com/pagespeedonline/v2/runPagespeed?${searchParams}`)
- const json = await res.json()
-
- this.setState({ error: false, loading: false, score: json.ruleGroups.SPEED.score })
- } catch (error) {
- this.setState({ error: true, loading: false })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- render () {
- const { error, loading, score } = this.state
- const { title } = this.props
- return (
-
-
-
- )
- }
-}
diff --git a/components/widgets/pagespeed-insights/stats.js b/components/widgets/pagespeed-insights/stats.js
deleted file mode 100644
index e92813d0..00000000
--- a/components/widgets/pagespeed-insights/stats.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import { Component } from 'react'
-import fetch from 'isomorphic-unfetch'
-import { object, string, boolean, number } from 'yup'
-import Table, { Th, Td } from '../../table'
-import Widget from '../../widget'
-
-const schema = object().shape({
- url: string().url().required(),
- filterThirdPartyResources: boolean(),
- interval: number(),
- strategy: string(),
- title: string()
-})
-
-export default class PageSpeedInsightsStats extends Component {
- static defaultProps = {
- filterThirdPartyResources: false,
- interval: 1000 * 60 * 60 * 12,
- strategy: 'desktop',
- title: 'PageSpeed Stats'
- }
-
- state = {
- stats: {},
- loading: true,
- error: false
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- bytesToKilobytes (bytes) {
- return bytes > 0 ? (bytes / 1024).toFixed(1) : 0
- }
-
- async fetchInformation () {
- const { url, filterThirdPartyResources, strategy } = this.props
-
- const searchParams = [
- `url=${url}`,
- `filter_third_party_resources=${filterThirdPartyResources}`,
- `strategy=${strategy}`
- ].join('&')
-
- try {
- const res = await fetch(`https://www.googleapis.com/pagespeedonline/v2/runPagespeed?${searchParams}`)
- const json = await res.json()
-
- const pageStats = json.pageStats
- const stats = {
- cssCount: pageStats.numberCssResources || 0,
- cssSize: this.bytesToKilobytes(pageStats.cssResponseBytes),
- htmlSize: this.bytesToKilobytes(pageStats.htmlResponseBytes),
- imageSize: this.bytesToKilobytes(pageStats.imageResponseBytes),
- javascriptCount: pageStats.numberJsResources || 0,
- javascriptSize: this.bytesToKilobytes(pageStats.javascriptResponseBytes),
- requestCount: pageStats.numberResources || 0,
- requestSize: this.bytesToKilobytes(pageStats.totalRequestBytes),
- otherSize: this.bytesToKilobytes(pageStats.otherResponseBytes)
- }
-
- this.setState({ error: false, loading: false, stats })
- } catch (error) {
- this.setState({ error: true, loading: false })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- render () {
- const { error, loading, stats } = this.state
- const { title } = this.props
- return (
-
-
-
-
- Request |
- {stats.requestSize} KB ({stats.requestCount}) |
-
-
-
- JavaScript |
- {stats.javascriptSize} KB ({stats.javascriptCount}) |
-
-
-
- CSS |
- {stats.cssSize} KB ({stats.cssCount}) |
-
-
-
- HTML |
- {stats.htmlSize} KB |
-
-
-
- Image |
- {stats.imageSize} KB |
-
-
-
- Other |
- {stats.otherSize} KB |
-
-
-
-
- )
- }
-}
diff --git a/components/widgets/sonarqube/SonarQube.tsx b/components/widgets/sonarqube/SonarQube.tsx
new file mode 100644
index 00000000..322cce04
--- /dev/null
+++ b/components/widgets/sonarqube/SonarQube.tsx
@@ -0,0 +1,205 @@
+import React, { Component } from "react";
+import styled from "styled-components";
+import fetch from "isomorphic-unfetch";
+import Widget from "../../Widget";
+import Table, { Td, Th } from "../../Table";
+import Badge from "../../Badge";
+import { basicAuthHeader } from "../../../lib/auth";
+import { ISonarQubeProps, ISonarQubeState } from "./sonarqube-model";
+
+const alertColor = ({ theme, children }) => {
+ switch (children) {
+ case "ERROR":
+ return theme.palette.errorColor;
+ case "WARN":
+ return theme.palette.warnColor;
+ default:
+ // OK
+ return theme.palette.successColor;
+ }
+};
+const Alert = styled.span`
+ color: ${alertColor};
+`;
+
+const sonarBadgeColor = ({ theme, children }) => {
+ switch (children) {
+ case "A":
+ return theme.palette.successColor;
+ case "B":
+ return theme.palette.successSecondaryColor;
+ case "C":
+ return theme.palette.warnColor;
+ case "D":
+ return theme.palette.warnSecondaryColor;
+ case "E":
+ return theme.palette.errorColor;
+ default:
+ return "transparent";
+ }
+};
+const SonarBadge = styled(Badge)`
+ background-color: ${sonarBadgeColor};
+`;
+
+export default class SonarQube extends Component<
+ ISonarQubeProps,
+ ISonarQubeState
+> {
+ static defaultProps = {
+ interval: 1000 * 60 * 5,
+ title: "SonarQube",
+ };
+
+ state = {
+ measures: [],
+ loading: true,
+ error: false,
+ };
+
+ timeout: any = 0;
+
+ componentDidMount() {
+ this.fetchInformation().catch((err) => {
+ console.error(`${err.name} @ ${this.constructor.name}`, err.errors);
+ this.setState({ error: true, loading: false });
+ });
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ async fetchInformation() {
+ const { authKey, url, componentKey } = this.props;
+ const opts = authKey ? { headers: basicAuthHeader(authKey) } : {};
+
+ // https://docs.sonarqube.org/display/SONAR/Metric+Definitions
+ const metricKeys = [
+ "alert_status",
+ "reliability_rating",
+ "bugs",
+ "security_rating",
+ "vulnerabilities",
+ "sqale_rating",
+ "code_smells",
+ "coverage",
+ "duplicated_lines_density",
+ ].join(",");
+
+ try {
+ 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 });
+ } finally {
+ this.timeout = setTimeout(
+ () => this.fetchInformation(),
+ this.props.interval
+ );
+ }
+ }
+
+ getMetricValue = (measures, metricKey: string) => {
+ const result = measures.filter((measure) => measure.metric === metricKey);
+ return result.length ? result[0].value : "";
+ };
+
+ getRatingValue = (measures, metricKey: string) => {
+ const value = this.getMetricValue(measures, metricKey);
+
+ switch (value) {
+ case "1.0":
+ return "A";
+ case "2.0":
+ return "B";
+ case "3.0":
+ return "C";
+ case "4.0":
+ return "D";
+ case "5.0":
+ return "E";
+ }
+
+ return "?";
+ };
+
+ render() {
+ const { error, loading, measures } = this.state;
+ const { title } = this.props;
+
+ const alertStatus = this.getMetricValue(measures, "alert_status");
+ const reliabilityRating = this.getRatingValue(
+ measures,
+ "reliability_rating"
+ );
+ const bugs = this.getMetricValue(measures, "bugs");
+ const securityRating = this.getRatingValue(measures, "security_rating");
+ const vulnerabilities = this.getMetricValue(measures, "vulnerabilities");
+ const sqaleRating = this.getRatingValue(measures, "sqale_rating");
+ const codeSmells = this.getMetricValue(measures, "code_smells");
+ const coverage = this.getMetricValue(measures, "coverage");
+ const duplicatedLinesDensity = this.getMetricValue(
+ measures,
+ "duplicated_lines_density"
+ );
+
+ return (
+
+
+
+
+ Quality Gate: |
+
+ {alertStatus}
+ |
+
+
+
+ Reliability: |
+
+ {reliabilityRating}{" "}
+ ({bugs})
+ |
+
+
+
+ Security: |
+
+ {securityRating}{" "}
+ ({vulnerabilities})
+ |
+
+
+
+ Maintainability: |
+
+ {sqaleRating}{" "}
+ ({codeSmells})
+ |
+
+
+
+ Coverage: |
+ {coverage}% |
+
+
+
+ Duplications: |
+ {duplicatedLinesDensity}% |
+
+
+
+
+ );
+ }
+}
diff --git a/components/widgets/sonarqube/index.js b/components/widgets/sonarqube/index.js
deleted file mode 100644
index 73f11cda..00000000
--- a/components/widgets/sonarqube/index.js
+++ /dev/null
@@ -1,182 +0,0 @@
-import { Component } from 'react'
-import styled from 'styled-components'
-import fetch from 'isomorphic-unfetch'
-import { object, string, number } from 'yup'
-import Widget from '../../widget'
-import Table, { Th, Td } from '../../table'
-import Badge from '../../badge'
-import { basicAuthHeader } from '../../../lib/auth'
-
-const alertColor = ({ theme, children }) => {
- switch (children) {
- case 'ERROR':
- return theme.palette.errorColor
- case 'WARN':
- return theme.palette.warnColor
- default: // OK
- return theme.palette.successColor
- }
-}
-const Alert = styled.span`
- color: ${alertColor};
-`
-
-const sonarBadgeColor = ({ theme, children }) => {
- switch (children) {
- case 'A':
- return theme.palette.successColor
- case 'B':
- return theme.palette.successSecondaryColor
- case 'C':
- return theme.palette.warnColor
- case 'D':
- return theme.palette.warnSecondaryColor
- case 'E':
- return theme.palette.errorColor
- default:
- return 'transparent'
- }
-}
-const SonarBadge = styled(Badge)`
- background-color: ${sonarBadgeColor};
-`
-
-const schema = object().shape({
- url: string().url().required(),
- componentKey: string().required(),
- interval: number(),
- title: string(),
- authKey: string()
-})
-
-export default class SonarQube extends Component {
- static defaultProps = {
- interval: 1000 * 60 * 5,
- title: 'SonarQube'
- }
-
- state = {
- measures: [],
- loading: true,
- error: false
- }
-
- componentDidMount () {
- schema.validate(this.props)
- .then(() => this.fetchInformation())
- .catch((err) => {
- console.error(`${err.name} @ ${this.constructor.name}`, err.errors)
- this.setState({ error: true, loading: false })
- })
- }
-
- componentWillUnmount () {
- clearTimeout(this.timeout)
- }
-
- async fetchInformation () {
- const { authKey, url, componentKey } = this.props
- const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}
-
- // https://docs.sonarqube.org/display/SONAR/Metric+Definitions
- const metricKeys = [
- 'alert_status', 'reliability_rating', 'bugs', 'security_rating',
- 'vulnerabilities', 'sqale_rating', 'code_smells', 'coverage',
- 'duplicated_lines_density'
- ].join(',')
-
- try {
- 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 })
- } finally {
- this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval)
- }
- }
-
- getMetricValue = (measures, metricKey) => {
- const result = measures.filter(measure => measure.metric === metricKey)
- return result.length ? result[0].value : ''
- }
-
- getRatingValue = (measures, metricKey) => {
- const value = this.getMetricValue(measures, metricKey)
-
- switch (value) {
- case '1.0':
- return 'A'
- case '2.0':
- return 'B'
- case '3.0':
- return 'C'
- case '4.0':
- return 'D'
- case '5.0':
- return 'E'
- }
-
- return '?'
- }
-
- render () {
- const { error, loading, measures } = this.state
- const { title } = this.props
-
- const alertStatus = this.getMetricValue(measures, 'alert_status')
- const reliabilityRating = this.getRatingValue(measures, 'reliability_rating')
- const bugs = this.getMetricValue(measures, 'bugs')
- const securityRating = this.getRatingValue(measures, 'security_rating')
- const vulnerabilities = this.getMetricValue(measures, 'vulnerabilities')
- const sqaleRating = this.getRatingValue(measures, 'sqale_rating')
- const codeSmells = this.getMetricValue(measures, 'code_smells')
- const coverage = this.getMetricValue(measures, 'coverage')
- const duplicatedLinesDensity = this.getMetricValue(measures, 'duplicated_lines_density')
-
- return (
-
-
-
-
- Quality Gate: |
- {alertStatus} |
-
-
-
- Reliability: |
-
- {reliabilityRating} ({bugs})
- |
-
-
-
- Security: |
-
- {securityRating} ({vulnerabilities})
- |
-
-
-
- Maintainability: |
-
- {sqaleRating} ({codeSmells})
- |
-
-
-
- Coverage: |
- {coverage}% |
-
-
-
- Duplications: |
- {duplicatedLinesDensity}% |
-
-
-
-
- )
- }
-}
diff --git a/components/widgets/sonarqube/sonarqube-model.ts b/components/widgets/sonarqube/sonarqube-model.ts
new file mode 100644
index 00000000..3d15293f
--- /dev/null
+++ b/components/widgets/sonarqube/sonarqube-model.ts
@@ -0,0 +1,13 @@
+export interface ISonarQubeProps {
+ url: string;
+ componentKey: string;
+ interval: number;
+ title: string;
+ authKey: string;
+}
+
+export interface ISonarQubeState {
+ measures: Array;
+ loading: boolean;
+ error: boolean;
+}
diff --git a/components/widgets/title/index.js b/components/widgets/title/Title.tsx
similarity index 63%
rename from components/widgets/title/index.js
rename to components/widgets/title/Title.tsx
index 268dc813..40cfd0d7 100644
--- a/components/widgets/title/index.js
+++ b/components/widgets/title/Title.tsx
@@ -1,4 +1,4 @@
-import styled from 'styled-components'
+import styled from "styled-components";
const Title = styled.h1`
flex: 1 1 100%;
@@ -6,6 +6,6 @@ const Title = styled.h1`
margin: 0;
padding: 0;
text-align: center;
-`
+`;
-export default Title
+export default Title;
diff --git a/lib/auth-model.ts b/lib/auth-model.ts
new file mode 100644
index 00000000..f940c50a
--- /dev/null
+++ b/lib/auth-model.ts
@@ -0,0 +1,4 @@
+export interface IAuthModel {
+ username: string;
+ password: string;
+}
diff --git a/lib/auth.js b/lib/auth.js
deleted file mode 100644
index 5456d9f0..00000000
--- a/lib/auth.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Base64 } from 'js-base64'
-import auth from '../auth'
-
-export const basicAuthHeader = (key) => {
- const credentials = auth[key]
-
- if (credentials) {
- const credential = Base64.encode(`${credentials.username}:${credentials.password}`)
- return { Authorization: `Basic ${credential}` }
- }
-
- throw new ReferenceError(`No credentials found with key '${key}' in auth.js`)
-}
diff --git a/lib/auth.ts b/lib/auth.ts
new file mode 100644
index 00000000..64a6110f
--- /dev/null
+++ b/lib/auth.ts
@@ -0,0 +1,16 @@
+import { Base64 } from "js-base64";
+import auth from "../auth";
+import { IAuthModel } from "./auth-model";
+
+export const basicAuthHeader = (key: string) => {
+ const credentials: IAuthModel = auth[key];
+
+ if (credentials) {
+ const credential: string = Base64.encode(
+ `${credentials.username}:${credentials.password}`
+ );
+ return { Authorization: `Basic ${credential}` };
+ }
+
+ throw new ReferenceError(`No credentials found with key '${key}' in auth.js`);
+};
diff --git a/next-env.d.ts b/next-env.d.ts
new file mode 100644
index 00000000..7b7aa2c7
--- /dev/null
+++ b/next-env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/next.config.js b/next.config.js
index e2cc2b56..c31edce6 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,10 +1,8 @@
-const Dotenv = require('dotenv-webpack')
+const Dotenv = require("dotenv-webpack");
module.exports = {
webpack: (config) => {
- config.plugins.push(
- new Dotenv({ path: './.env' })
- )
- return config
- }
-}
+ config.plugins.push(new Dotenv({ path: "./.env" }));
+ return config;
+ },
+};
diff --git a/package.json b/package.json
index fda4d7d2..94c4b975 100644
--- a/package.json
+++ b/package.json
@@ -2,13 +2,14 @@
"name": "dashboard",
"version": "1.0.0",
"description": "Your Team Dashboard",
- "main": "pages/index.js",
+ "main": "pages/index.tsx",
"private": true,
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
- "lint": "standard --verbose | snazzy && stylelint '**/*.js'",
+ "lint": "prettier --check *.js **/*.tsx **/**/*.tsx **/**/*.ts",
+ "format": "prettier --write *.js **/*.tsx **/**/*.tsx **/**/*.ts",
"test": "npm run lint"
},
"repository": {
@@ -26,6 +27,8 @@
},
"homepage": "https://github.com/danielbayerlein/dashboard#readme",
"dependencies": {
+ "@types/node": "^13.9.2",
+ "@types/react": "^16.9.25",
"babel-plugin-styled-components": "^1.10.7",
"dotenv-webpack": "^1.7.0",
"isomorphic-unfetch": "^3.0.0",
@@ -36,7 +39,9 @@
"react-dom": "^16.13.1",
"styled-components": "^4.4.1",
"tinytime": "^0.2.6",
- "yup": "^0.28.1"
+ "typescript": "^3.8.3",
+ "yup": "^0.28.1",
+ "prettier": "^2.0.1"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
diff --git a/pages/MyDocument.tsx b/pages/MyDocument.tsx
new file mode 100644
index 00000000..00b01870
--- /dev/null
+++ b/pages/MyDocument.tsx
@@ -0,0 +1,31 @@
+import Document from "next/document";
+import { ServerStyleSheet } from "styled-components";
+import React from "react";
+
+export default class MyDocument extends Document {
+ static async getInitialProps(ctx) {
+ const sheet = new ServerStyleSheet();
+ const originalRenderPage = ctx.renderPage;
+
+ try {
+ ctx.renderPage = () =>
+ originalRenderPage({
+ enhanceApp: (App) => (props) =>
+ sheet.collectStyles(),
+ });
+
+ const initialProps = await Document.getInitialProps(ctx);
+ return {
+ ...initialProps,
+ styles: (
+ <>
+ {initialProps.styles}
+ {sheet.getStyleElement()}
+ >
+ ),
+ };
+ } finally {
+ sheet.seal();
+ }
+ }
+}
diff --git a/pages/_document.js b/pages/_document.js
deleted file mode 100644
index 542898c9..00000000
--- a/pages/_document.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import Document from 'next/document'
-import { ServerStyleSheet } from 'styled-components'
-
-export default class MyDocument extends Document {
- static async getInitialProps (ctx) {
- const sheet = new ServerStyleSheet()
- const originalRenderPage = ctx.renderPage
-
- try {
- ctx.renderPage = () =>
- originalRenderPage({
- enhanceApp: App => props => sheet.collectStyles()
- })
-
- const initialProps = await Document.getInitialProps(ctx)
- return {
- ...initialProps,
- styles: <>{initialProps.styles}{sheet.getStyleElement()}>
- }
- } finally {
- sheet.seal()
- }
- }
-}
diff --git a/pages/index.js b/pages/index.js
deleted file mode 100644
index c8637a16..00000000
--- a/pages/index.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import Dashboard from '../components/dashboard'
-
-// Widgets
-import DateTime from '../components/widgets/datetime'
-import PageSpeedInsightsScore from '../components/widgets/pagespeed-insights/score'
-import PageSpeedInsightsStats from '../components/widgets/pagespeed-insights/stats'
-import JiraIssueCount from '../components/widgets/jira/issue-count'
-import SonarQube from '../components/widgets/sonarqube'
-import JenkinsJobStatus from '../components/widgets/jenkins/job-status'
-import JenkinsJobHealth from '../components/widgets/jenkins/job-health'
-import JenkinsBuildDuration from '../components/widgets/jenkins/build-duration'
-import BitbucketPullRequestCount from '../components/widgets/bitbucket/pull-request-count'
-import ElasticsearchHitCount from '../components/widgets/elasticsearch/hit-count'
-import GitHubIssueCount from '../components/widgets/github/issue-count'
-
-// Theme
-import lightTheme from '../styles/light-theme'
-// import darkTheme from '../styles/dark-theme'
-
-export default () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
diff --git a/pages/index.tsx b/pages/index.tsx
new file mode 100644
index 00000000..40196fb5
--- /dev/null
+++ b/pages/index.tsx
@@ -0,0 +1,92 @@
+import React from "react";
+
+import Dashboard from "../components/Dashboard";
+
+// Widgets
+import DateTime from "../components/widgets/datetime/DateTime";
+import PageSpeedInsightsScore from "../components/widgets/pagespeed-insights/PageSpeedInsideScore";
+import PageSpeedInsightsStats from "../components/widgets/pagespeed-insights/PageSpeedInsightsStats";
+import JiraIssueCount from "../components/widgets/jira/JiraIssueCount";
+import SonarQube from "../components/widgets/sonarqube/SonarQube";
+import JenkinsJobStatus from "../components/widgets/jenkins/JenkinsJobStatus";
+import JenkinsJobHealth from "../components/widgets/jenkins/JenkinsJobHealth";
+import JenkinsBuildDuration from "../components/widgets/jenkins/JenkinsBuildBuration";
+import BitbucketPullRequestCount from "../components/widgets/bitbucket/BitbucketPullRequestCount";
+import ElasticsearchHitCount from "../components/widgets/elasticsearch/ElasticsearchHitCount";
+import GitHubIssueCount from "../components/widgets/github/GitHubIssueCount";
+
+// Theme
+import lightTheme from "../styles/light-theme";
+// import darkTheme from '../styles/dark-theme'
+
+export default () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/styles/dark-theme.js b/styles/dark-theme.js
deleted file mode 100644
index f76f3ffd..00000000
--- a/styles/dark-theme.js
+++ /dev/null
@@ -1,32 +0,0 @@
-const colors = {
- grey400: '#bdbdbd',
- grey700: '#616161',
- grey800: '#424242',
- grey: '#303030',
- white: '#ffffff',
- cyan500: '#00bcd4',
- pinkA200: '#ff4081',
- red500: '#f44336',
- amber500: '#ffc107',
- green500: '#4caf50',
- orange500: '#ff9800',
- lime500: '#cddc39'
-}
-
-export default {
- palette: {
- backgroundColor: colors.grey,
- borderColor: colors.grey700,
- textColor: colors.white,
- textInvertColor: colors.grey,
- canvasColor: colors.grey800,
- primaryColor: colors.cyan500,
- accentColor: colors.pinkA200,
- errorColor: colors.red500,
- warnColor: colors.amber500,
- warnSecondaryColor: colors.orange500,
- successColor: colors.green500,
- successSecondaryColor: colors.lime500,
- disabledColor: colors.grey400
- }
-}
diff --git a/styles/dark-theme.ts b/styles/dark-theme.ts
new file mode 100644
index 00000000..13ae4f9a
--- /dev/null
+++ b/styles/dark-theme.ts
@@ -0,0 +1,36 @@
+import { IThemePalette } from "./theme-model";
+
+const colors = {
+ grey400: "#bdbdbd",
+ grey700: "#616161",
+ grey800: "#424242",
+ grey: "#303030",
+ white: "#ffffff",
+ cyan500: "#00bcd4",
+ pinkA200: "#ff4081",
+ red500: "#f44336",
+ amber500: "#ffc107",
+ green500: "#4caf50",
+ orange500: "#ff9800",
+ lime500: "#cddc39",
+};
+
+const palette: IThemePalette = {
+ backgroundColor: colors.grey,
+ borderColor: colors.grey700,
+ textColor: colors.white,
+ textInvertColor: colors.grey,
+ canvasColor: colors.grey800,
+ primaryColor: colors.cyan500,
+ accentColor: colors.pinkA200,
+ errorColor: colors.red500,
+ warnColor: colors.amber500,
+ warnSecondaryColor: colors.orange500,
+ successColor: colors.green500,
+ successSecondaryColor: colors.lime500,
+ disabledColor: colors.grey400,
+};
+
+export default {
+ palette: palette,
+};
diff --git a/styles/light-theme.js b/styles/light-theme.js
deleted file mode 100644
index 0585f1d1..00000000
--- a/styles/light-theme.js
+++ /dev/null
@@ -1,32 +0,0 @@
-const colors = {
- grey50: '#fafafa',
- grey200: '#eeeeee',
- grey400: '#bdbdbd',
- grey900: '#212121',
- white: '#ffffff',
- cyan500: '#00bcd4',
- pinkA200: '#ff4081',
- redA700: '#d50000',
- amberA700: '#ffab00',
- greenA700: '#00c853',
- lightGreenA700: '#64dd17',
- orangeA700: '#ff6d00'
-}
-
-export default {
- palette: {
- backgroundColor: colors.grey50,
- borderColor: colors.grey200,
- textColor: colors.grey900,
- textInvertColor: colors.grey50,
- canvasColor: colors.white,
- primaryColor: colors.cyan500,
- accentColor: colors.pinkA200,
- errorColor: colors.redA700,
- warnColor: colors.amberA700,
- warnSecondaryColor: colors.orangeA700,
- successColor: colors.greenA700,
- successSecondaryColor: colors.lightGreenA700,
- disabledColor: colors.grey400
- }
-}
diff --git a/styles/light-theme.ts b/styles/light-theme.ts
new file mode 100644
index 00000000..654aa623
--- /dev/null
+++ b/styles/light-theme.ts
@@ -0,0 +1,36 @@
+import { IThemePalette } from "./theme-model";
+
+const colors = {
+ grey50: "#fafafa",
+ grey200: "#eeeeee",
+ grey400: "#bdbdbd",
+ grey900: "#212121",
+ white: "#ffffff",
+ cyan500: "#00bcd4",
+ pinkA200: "#ff4081",
+ redA700: "#d50000",
+ amberA700: "#ffab00",
+ greenA700: "#00c853",
+ lightGreenA700: "#64dd17",
+ orangeA700: "#ff6d00",
+};
+
+const palette: IThemePalette = {
+ backgroundColor: colors.grey50,
+ borderColor: colors.grey200,
+ textColor: colors.grey900,
+ textInvertColor: colors.grey50,
+ canvasColor: colors.white,
+ primaryColor: colors.cyan500,
+ accentColor: colors.pinkA200,
+ errorColor: colors.redA700,
+ warnColor: colors.amberA700,
+ warnSecondaryColor: colors.orangeA700,
+ successColor: colors.greenA700,
+ successSecondaryColor: colors.lightGreenA700,
+ disabledColor: colors.grey400,
+};
+
+export default {
+ palette: palette,
+};
diff --git a/styles/theme-model.ts b/styles/theme-model.ts
new file mode 100644
index 00000000..3bb8189e
--- /dev/null
+++ b/styles/theme-model.ts
@@ -0,0 +1,15 @@
+export interface IThemePalette {
+ backgroundColor: string;
+ borderColor: string;
+ textColor: string;
+ textInvertColor: string;
+ canvasColor: string;
+ primaryColor: string;
+ accentColor: string;
+ errorColor: string;
+ warnColor: string;
+ warnSecondaryColor: string;
+ successColor: string;
+ successSecondaryColor: string;
+ disabledColor: string;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..48ca14af
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve"
+ },
+ "exclude": [
+ "node_modules"
+ ],
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx"
+ ]
+}
diff --git a/yarn.lock b/yarn.lock
index a81d4c2a..f0a4af7f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1184,6 +1184,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e"
integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ==
+"@types/node@^13.9.2":
+ version "13.9.2"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349"
+ integrity sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg==
+
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
@@ -1194,6 +1199,19 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
+"@types/prop-types@*":
+ version "15.7.3"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
+ integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
+
+"@types/react@^16.9.25":
+ version "16.9.25"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.25.tgz#6ae2159b40138c792058a23c3c04fd3db49e929e"
+ integrity sha512-Dlj2V72cfYLPNscIG3/SMUOzhzj7GK3bpSrfefwt2YT9GLynvLCCZjbhyF6VsT0q0+aRACRX03TDJGb7cA0cqg==
+ dependencies:
+ "@types/prop-types" "*"
+ csstype "^2.2.0"
+
"@types/unist@*", "@types/unist@^2.0.0":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.2.tgz#5dc0a7f76809b7518c0df58689cd16a19bd751c6"
@@ -2699,6 +2717,11 @@ cssnano-simple@1.0.0:
cssnano-preset-simple "^1.0.0"
postcss "^7.0.18"
+csstype@^2.2.0:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
+ integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
+
cyclist@~0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
@@ -6521,6 +6544,11 @@ prepend-http@^1.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
+prettier@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.1.tgz#3f00ac71263be34684b2b2c8d7e7f63737592dac"
+ integrity sha512-piXGBcY1zoFOG0MvHpNE5reAGseLmaCRifQ/fmfF49BcYkInEs/naD/unxGNAeOKFA5+JxVrPyMvMlpzcd20UA==
+
private@^0.1.6, private@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -8125,6 +8153,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+typescript@^3.8.3:
+ version "3.8.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
+ integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==
+
ua-parser-js@^0.7.18:
version "0.7.18"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed"