gitlab-radiator 3.4.0 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.babelrc DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "presets": ["@babel/env", "@babel/react"],
3
- "plugins": ["@babel/plugin-transform-runtime"]
4
- }
package/.eslintrc DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "env": {
3
- "es6": true,
4
- "mocha": true,
5
- "node": true
6
- },
7
- "parser": "@babel/eslint-parser",
8
- "plugins": [
9
- "mocha",
10
- "react"
11
- ],
12
- "extends": ["eslint:recommended"]
13
- }
@@ -1,20 +0,0 @@
1
- name: Run tests
2
-
3
- on: [push, pull_request]
4
-
5
- jobs:
6
- build:
7
- runs-on: ubuntu-latest
8
- strategy:
9
- matrix:
10
- node-version: [12.x, 14.x]
11
- steps:
12
- - uses: actions/checkout@v2
13
- - uses: actions/setup-node@v1
14
- with:
15
- node-version: ${{ matrix.node-version }}
16
- - run: npm ci
17
- - run: npm audit
18
- - run: npm run eslint
19
- - run: npm test
20
- - run: npm run build
package/.nvmrc DELETED
@@ -1 +0,0 @@
1
- 16.17.0
package/build-npm DELETED
@@ -1,19 +0,0 @@
1
- #!/bin/bash
2
-
3
- rm -fr build
4
- mkdir -p build/src
5
-
6
- # Copy static resources
7
- cp -r public build
8
-
9
- # Copy LICENSE, README and package.json
10
- cp LICENSE package.json README.md build
11
-
12
- # Copy bin script
13
- cp -r bin build
14
-
15
- # Bundle and minify client JS
16
- npx webpack --config webpack.prod.js
17
-
18
- # Transpile server
19
- node_modules/.bin/babel src --ignore **/client/*.js,**/dev-assets.js --out-dir build/src
package/screenshot.png DELETED
Binary file
@@ -1,21 +0,0 @@
1
- {
2
- "env": {
3
- "browser": true,
4
- "es6": true
5
- },
6
- "parser": "@typescript-eslint/parser",
7
- "plugins": [
8
- "@typescript-eslint"
9
- ],
10
- "extends": [
11
- "eslint:recommended",
12
- "plugin:react/recommended",
13
- "plugin:@typescript-eslint/eslint-recommended",
14
- "plugin:@typescript-eslint/recommended"
15
- ],
16
- "settings": {
17
- "react": {
18
- "version": "17.0"
19
- }
20
- }
21
- }
@@ -1,75 +0,0 @@
1
- interface ParsedQueryString {
2
- [key: string]: string | undefined
3
- }
4
-
5
- export function argumentsFromDocumentUrl(): {override: {columns?: number, zoom?: number}, includedTags: string[] | null, screen: {id: number, total: number}} {
6
- const args = parseQueryString(document.location.search)
7
- return {
8
- override: overrideArguments(args),
9
- includedTags: tagArguments(args),
10
- screen: screenArguments(args)
11
- }
12
- }
13
-
14
- function tagArguments(args: ParsedQueryString): string[] | null {
15
- if (args.tags === undefined) {
16
- return null
17
- }
18
- return args.tags
19
- .split(',')
20
- .map(t => t.toLowerCase().trim())
21
- .filter(t => t)
22
- }
23
-
24
- function overrideArguments(args: ParsedQueryString): {columns?: number, zoom?: number} {
25
- return {
26
- ...parseColumns(args),
27
- ...parseZoom(args)
28
- }
29
- }
30
-
31
- function parseColumns(args: ParsedQueryString) {
32
- if (args.columns) {
33
- const columns = Number(args.columns)
34
- if (columns > 0 && columns <= 10) {
35
- return {columns}
36
- }
37
- }
38
- return {}
39
- }
40
-
41
-
42
- function parseZoom(args: ParsedQueryString) {
43
- if (args.zoom) {
44
- const zoom = Number(args.zoom)
45
- if (zoom > 0 && zoom <= 2) {
46
- return {zoom}
47
- }
48
- }
49
- return {}
50
- }
51
-
52
- function screenArguments(args: ParsedQueryString): {id: number, total: number} {
53
- const matches = (/(\d)of(\d)/).exec(args.screen || '')
54
- let id = matches ? Number(matches[1]) : 1
55
- const total = matches ? Number(matches[2]) : 1
56
- if (id > total) {
57
- id = total
58
- }
59
- return {
60
- id,
61
- total
62
- }
63
- }
64
-
65
- function parseQueryString(search: string): ParsedQueryString {
66
- const entries = search
67
- .slice(1)
68
- .split('&')
69
- .filter(parameter => parameter)
70
- .map((parameter: string): [string, string | undefined] => {
71
- const [key, value] = parameter.split('=')
72
- return [key, value]
73
- })
74
- return Object.fromEntries(entries)
75
- }
@@ -1,54 +0,0 @@
1
-
2
- export interface GlobalState {
3
- columns: number
4
- error: string | null
5
- groupSuccessfulProjects: boolean
6
- projects: Project[] | null
7
- projectsOrder: string[]
8
- zoom: number
9
- now: number
10
- }
11
-
12
- export interface Project {
13
- archived: false
14
- group: string
15
- id: number
16
- name: string
17
- nameWithoutNamespace: string
18
- tags: string[]
19
- url: string
20
- default_branch: string
21
- pipelines: Pipeline[]
22
- maxNonFailedJobsVisible: number
23
- status: 'success' | 'failed'
24
- }
25
-
26
- export interface Pipeline {
27
- commit: Commit | null
28
- id: number
29
- ref: string
30
- stages: Stage[]
31
- status: 'success' | 'failed'
32
- }
33
-
34
- export interface Commit {
35
- title: string
36
- author: string
37
- }
38
-
39
- export interface Stage {
40
- jobs: Job[]
41
- name: string
42
- }
43
-
44
- export interface Job {
45
- finishedAt: string | null
46
- id: number
47
- name: string
48
- stage: string
49
- startedAt: string | null
50
- status: JobStatus
51
- url: string
52
- }
53
-
54
- export type JobStatus = 'created' | 'failed' | 'manual' | 'pending' | 'running' | 'skipped' | 'success'
@@ -1,40 +0,0 @@
1
- import _ from 'lodash'
2
- import {Groups} from './groups'
3
- import type {Project} from './gitlab-types'
4
- import {Projects} from './projects'
5
- import React from 'react'
6
-
7
- export function GroupedProjects({projects, projectsOrder, groupSuccessfulProjects, zoom, columns, now, screen}: {projects: Project[], projectsOrder: string[], groupSuccessfulProjects: boolean, zoom: number, columns: number, now: number, screen: {id: number, total: number}}): JSX.Element {
8
- if (groupSuccessfulProjects) {
9
- return renderProjectsGrouped(projects, projectsOrder, zoom, columns, now, screen)
10
- }
11
- return renderProjects(projects, projectsOrder, zoom, columns, now, screen)
12
- }
13
-
14
- function renderProjectsGrouped(projects: Project[], projectsOrder: string[], zoom: number, columns: number, now: number, screen: {id: number, total: number}) {
15
- const successfullProjects: Project[] = []
16
- const otherProjects: Project[] = []
17
- projects.forEach((project) => {
18
- if (project.status === 'success') {
19
- successfullProjects.push(project)
20
- } else {
21
- otherProjects.push(project)
22
- }
23
- })
24
- const groupedProjects = _.groupBy(successfullProjects, 'group')
25
- return <React.Fragment>
26
- {renderProjects(otherProjects, projectsOrder, zoom, columns, now, screen)}
27
- {renderGroupedProjects(groupedProjects, zoom, columns, now)}
28
- </React.Fragment>
29
- }
30
-
31
- function renderProjects(projects: Project[], projectsOrder: string[], zoom: number, columns: number, now: number, screen: {id: number, total: number}) {
32
- return <Projects now={now} zoom={zoom} columns={columns}
33
- projects={projects || []} projectsOrder={projectsOrder}
34
- screen={screen}/>
35
- }
36
-
37
- function renderGroupedProjects(groupedProjects: {[groupname: string]: Project[]}, zoom: number, columns: number, now: number) {
38
- return <Groups zoom={zoom} columns={columns} now={now}
39
- groupedProjects={groupedProjects || []} />
40
- }
@@ -1,59 +0,0 @@
1
- import type {Pipeline, Project} from './gitlab-types'
2
- import React from 'react'
3
- import {renderTimestamp} from './renderTimestamp'
4
-
5
- export function Groups({groupedProjects, now, zoom, columns}: {groupedProjects: {[groupname: string]: Project[]}, now: number, zoom: number, columns: number}): JSX.Element {
6
- return <ol className="groups" style={zoomStyle(zoom)}>
7
- {Object
8
- .entries(groupedProjects)
9
- .sort(([groupName1], [groupName2]) => groupName1.localeCompare(groupName2))
10
- .map(([groupName, projects]) => <GroupElement columns={columns} groupName={groupName} key={groupName} projects={projects} now={now}/>)
11
- }
12
- </ol>
13
- }
14
-
15
- function GroupElement({groupName, projects, now, columns}: {groupName: string, projects: Project[], now: number, columns: number}) {
16
- const pipelines: (Pipeline & {project: string})[] = []
17
- projects.forEach((project) => {
18
- project.pipelines.forEach((pipeline) => {
19
- pipelines.push({
20
- ...pipeline,
21
- project: project.nameWithoutNamespace
22
- })
23
- })
24
- })
25
-
26
- return <li className={'group'} style={style(columns)}>
27
- <h2>{groupName}</h2>
28
- <div className={'group-info'}>{projects.length} Project{projects.length > 1 ? 's' : ''}</div>
29
- <GroupInfoElement now={now} pipeline={pipelines[0]}/>
30
- </li>
31
- }
32
-
33
- function GroupInfoElement({now, pipeline}: {now: number, pipeline: (Pipeline & {project: string})}) {
34
- return <div className="pipeline-info">
35
- <div>
36
- <span>{pipeline.commit ? pipeline.commit.author : '-'}</span>
37
- <span>{pipeline.commit ? pipeline.project : '-'}</span>
38
- </div>
39
- <div>
40
- <span>{renderTimestamp(pipeline.stages, now)}</span>
41
- <span>on {pipeline.ref}</span>
42
- </div>
43
- </div>
44
- }
45
-
46
- function zoomStyle(zoom: number) {
47
- const widthPercentage = Math.round(100 / zoom)
48
- return {
49
- transform: `scale(${zoom})`,
50
- width: `${widthPercentage}vmax`
51
- }
52
- }
53
-
54
- function style(columns: number) {
55
- const widthPercentage = Math.round(90 / columns)
56
- return {
57
- width: `${widthPercentage}%`
58
- }
59
- }
@@ -1,95 +0,0 @@
1
- import 'core-js/stable'
2
- import 'regenerator-runtime/runtime'
3
-
4
- import type {GlobalState, Project} from './gitlab-types'
5
- import {argumentsFromDocumentUrl} from './arguments'
6
- import {createRoot} from 'react-dom/client'
7
- import {GroupedProjects} from './groupedProjects'
8
- import React from 'react'
9
-
10
- class RadiatorApp extends React.Component<unknown, GlobalState> {
11
- public args: {override: {columns?: number, zoom?: number}, includedTags: string[] | null, screen: {id: number, total: number}}
12
-
13
- constructor(props: unknown) {
14
- super(props)
15
- this.state = {
16
- columns: 1,
17
- error: null,
18
- groupSuccessfulProjects: false,
19
- projects: null,
20
- projectsOrder: [],
21
- now: 0,
22
- zoom: 1
23
- }
24
-
25
- this.args = argumentsFromDocumentUrl()
26
- }
27
-
28
- componentDidMount = () => {
29
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
- const socket = (window as any).io()
31
- socket.on('state', this.onServerStateUpdated.bind(this))
32
- socket.on('disconnect', this.onDisconnect.bind(this))
33
- }
34
-
35
- render = () =>
36
- <div>
37
- {this.renderErrorMessage()}
38
- {this.renderProgressMessage()}
39
-
40
- {this.state.projects &&
41
- <GroupedProjects now={this.state.now} zoom={this.state.zoom} columns={this.state.columns}
42
- projects={this.state.projects} projectsOrder={this.state.projectsOrder}
43
- groupSuccessfulProjects={this.state.groupSuccessfulProjects}
44
- screen={this.args.screen}/>
45
- }
46
- </div>
47
-
48
- renderErrorMessage = () =>
49
- this.state.error && <div className="error">{this.state.error}</div>
50
-
51
- renderProgressMessage = () => {
52
- if (!this.state.projects) {
53
- return <h2 className="loading">Fetching projects and CI pipelines from GitLab...</h2>
54
- } else if (this.state.projects.length === 0) {
55
- return <h2 className="loading">No projects with CI pipelines found.</h2>
56
- }
57
- return null
58
- }
59
-
60
- onServerStateUpdated = (state: GlobalState) => {
61
- this.setState({
62
- ...state,
63
- ...this.args.override,
64
- projects: this.filterProjectsByTags(state.projects)
65
- })
66
- }
67
-
68
- onDisconnect = () => this.setState({error: 'gitlab-radiator server is offline'})
69
-
70
- filterProjectsByTags = (projects: Project[] | null) => {
71
- if (projects === null) {
72
- return null
73
- }
74
-
75
- // No tag list specified, include all projects
76
- if (!this.args.includedTags) {
77
- return projects
78
- }
79
- // Empty tag list specified, include projects without tags
80
- if (this.args.includedTags.length === 0) {
81
- return projects.filter(project =>
82
- project.tags.length === 0
83
- )
84
- }
85
- // Tag list specified, include projects which have at least one of them
86
- return projects.filter(project =>
87
- project.tags.some(tag => this.args.includedTags?.includes(tag))
88
- )
89
- }
90
- }
91
-
92
- const root = createRoot(document.getElementById('app')!)
93
- root.render(<RadiatorApp/>);
94
-
95
- module.hot?.accept()
@@ -1,16 +0,0 @@
1
- import type {Pipeline} from './gitlab-types'
2
- import React from 'react'
3
- import {renderTimestamp} from './renderTimestamp'
4
-
5
- export function Info({pipeline, now}: {pipeline: Pipeline, now: number}): JSX.Element {
6
- return <div className="pipeline-info">
7
- <div>
8
- <span>{pipeline.commit ? pipeline.commit.author : '-'}</span>
9
- <span>{pipeline.commit ? `'${pipeline.commit.title}'` : '-'}</span>
10
- </div>
11
- <div>
12
- <span>{renderTimestamp(pipeline.stages, now)}</span>
13
- <span>on {pipeline.ref}</span>
14
- </div>
15
- </div>
16
- }
@@ -1,56 +0,0 @@
1
- import type {Job, JobStatus} from './gitlab-types'
2
- import _ from 'lodash'
3
- import React from 'react'
4
-
5
- const NON_BREAKING_SPACE = '\xa0'
6
-
7
- const JOB_STATES_IN_INTEREST_ORDER: JobStatus[] = [
8
- 'failed',
9
- 'running',
10
- 'created',
11
- 'pending',
12
- 'success',
13
- 'skipped'
14
- ]
15
-
16
- export function Jobs({jobs, maxNonFailedJobsVisible}: {jobs: Job[], maxNonFailedJobsVisible: number}): JSX.Element {
17
- const [failedJobs, nonFailedJobs] = _.partition(jobs, {status: 'failed'})
18
- const filteredJobs = sortByOriginalOrder(
19
- failedJobs.concat(
20
- _.orderBy(nonFailedJobs, ({status}) => JOB_STATES_IN_INTEREST_ORDER.indexOf(status))
21
- .slice(0, Math.max(0, maxNonFailedJobsVisible - failedJobs.length))
22
- ),
23
- jobs
24
- )
25
-
26
- const hiddenJobs = jobs.filter(job => filteredJobs.indexOf(job) === -1)
27
- const hiddenCountsByStatus = _.mapValues(
28
- _.groupBy(hiddenJobs, 'status'),
29
- jobsForStatus => jobsForStatus.length
30
- )
31
-
32
- const hiddenJobsText = _(hiddenCountsByStatus)
33
- .toPairs()
34
- .orderBy(([status]) => status)
35
- .value()
36
- .map(([status, count]) => `${count}${NON_BREAKING_SPACE}${status}`)
37
- .join(', ')
38
-
39
- return <ol className="jobs">
40
- {filteredJobs.map(job => <JobElement job={job} key={job.id}/>)}
41
- {
42
- hiddenJobs.length > 0 ? <li className="hidden-jobs">+&nbsp;{hiddenJobsText}</li> : null
43
- }
44
- </ol>
45
- }
46
-
47
- function JobElement({job}: {job: Job}) {
48
- return <li className={job.status}>
49
- <a href={job.url} target="_blank" rel="noopener noreferrer">{job.name}</a>
50
- {!job.url && job.name}
51
- </li>
52
- }
53
-
54
- function sortByOriginalOrder(filteredJobs: Job[], jobs: Job[]) {
55
- return _.orderBy(filteredJobs, (job: Job) => jobs.indexOf(job))
56
- }
@@ -1,51 +0,0 @@
1
- import _ from 'lodash'
2
- import {Info} from './info'
3
- import type {Project} from './gitlab-types'
4
- import React from 'react'
5
- import {Stages} from './stages'
6
-
7
- export function Projects({columns, now, projects, projectsOrder, screen, zoom}: {columns: number, now: number, projects: Project[], projectsOrder: string[], screen: {id: number, total: number}, zoom: number}): JSX.Element {
8
- return <ol className="projects" style={zoomStyle(zoom)}>
9
- {_.sortBy(projects, projectsOrder)
10
- .filter(forScreen(screen, projects.length))
11
- .map(project => <ProjectElement now={now} columns={columns} project={project} key={project.id}/>)
12
- }
13
- </ol>
14
- }
15
-
16
- function ProjectElement({columns, now, project}: {columns: number, now: number, project: Project}) {
17
- const [pipeline] = project.pipelines
18
-
19
- return <li className={`project ${project.status}`} style={style(columns)}>
20
- <h2>
21
- {project.url && <a href={`${project.url}/pipelines`} target="_blank" rel="noopener noreferrer">{project.name}</a>}
22
- {!project.url && project.name}
23
- </h2>
24
- <Stages stages={pipeline.stages} maxNonFailedJobsVisible={project.maxNonFailedJobsVisible}/>
25
- <Info now={now} pipeline={pipeline}/>
26
- </li>
27
- }
28
-
29
- function forScreen(screen: {id: number, total: number}, projectsCount: number) {
30
- const perScreen = Math.ceil(projectsCount / screen.total)
31
- const first = perScreen * (screen.id - 1)
32
- const last = perScreen * screen.id
33
- return (_project: Project, projectIndex: number) => projectIndex >= first && projectIndex < last
34
- }
35
-
36
- function zoomStyle(zoom: number) {
37
- const widthPercentage = Math.round(100 / zoom)
38
- return {
39
- transform: `scale(${zoom})`,
40
- width: `${widthPercentage}vmax`
41
- }
42
- }
43
-
44
- function style(columns: number) {
45
- const marginPx = 12
46
- const widthPercentage = Math.floor(100 / columns)
47
- return {
48
- margin: `${marginPx}px`,
49
- width: `calc(${widthPercentage}% - ${2 * marginPx}px)`
50
- }
51
- }
@@ -1,45 +0,0 @@
1
- import {formatDistance} from 'date-fns'
2
- import type {Stage} from './gitlab-types'
3
-
4
- export function renderTimestamp(stages: Stage[], now: number): string {
5
- const timestamps = getTimestamps(stages)
6
-
7
- if (timestamps.length === 0) {
8
- return 'Pending...'
9
- }
10
-
11
- const finished = timestamps
12
- .map(t => t.finishedAt)
13
- .filter((t): t is number => t !== null)
14
- const inProgress = timestamps.length > finished.length
15
- if (inProgress) {
16
- const [timestamp] = timestamps.sort((a, b) => a.startedAt - b.startedAt)
17
- return renderDistance('Started', timestamp.startedAt, now)
18
- }
19
-
20
- const [latestFinishedAt] = finished.sort((a, b) => b - a)
21
- return renderDistance('Finished', latestFinishedAt, now)
22
- }
23
-
24
- function getTimestamps(stages: Stage[]): {startedAt: number, finishedAt: number | null}[] {
25
- return stages
26
- .flatMap(s => s.jobs)
27
- .map(job => {
28
- const startedAt = job.startedAt ? new Date(job.startedAt).valueOf() : null
29
- const finishedAt = job.finishedAt ? new Date(job.finishedAt).valueOf() : null
30
- return {
31
- startedAt,
32
- finishedAt
33
- }
34
- })
35
- .filter((t): t is {startedAt: number, finishedAt: number | null} => t.startedAt !== null)
36
- }
37
-
38
- function renderDistance(predicate: string, timestamp: number, now: number) {
39
- const distance = formatDate(timestamp, now)
40
- return `${predicate} ${distance} ago`
41
- }
42
-
43
- function formatDate(timestamp: number, now: number) {
44
- return formatDistance(new Date(timestamp), new Date(now))
45
- }
@@ -1,18 +0,0 @@
1
- import {Jobs} from './jobs'
2
- import React from 'react'
3
- import type {Stage} from './gitlab-types'
4
-
5
- export function Stages({stages, maxNonFailedJobsVisible}: {stages: Stage[], maxNonFailedJobsVisible: number}): JSX.Element {
6
- return <ol className="stages">
7
- {stages.map(stage =>
8
- <StageElement stage={stage} maxNonFailedJobsVisible={maxNonFailedJobsVisible} key={stage.name}/>
9
- )}
10
- </ol>
11
- }
12
-
13
- function StageElement({stage, maxNonFailedJobsVisible}: {stage: Stage, maxNonFailedJobsVisible: number}) {
14
- return <li className="stage">
15
- <div className="name">{stage.name}</div>
16
- <Jobs jobs={stage.jobs} maxNonFailedJobsVisible={maxNonFailedJobsVisible}/>
17
- </li>
18
- }
package/src/dev-assets.js DELETED
@@ -1,10 +0,0 @@
1
- import config from '../webpack.dev.js'
2
- import webpack from 'webpack'
3
- import webpackDevMiddleware from 'webpack-dev-middleware'
4
- import webpackHotMiddleware from 'webpack-hot-middleware'
5
-
6
- export function bindDevAssets(app) {
7
- const compiler = webpack(config)
8
- app.use(webpackDevMiddleware(compiler))
9
- app.use(webpackHotMiddleware(compiler))
10
- }