├── .eslintignore ├── client.js ├── .babelrc ├── preview ├── project.png ├── gitlab_branches.png ├── project_members.png ├── gitlab_labels_pie.png ├── sample_dashboard.png ├── gitlab_build_history.png ├── gitlab_labels_bubble.png ├── gitlab_build_histogram.png ├── gitlab_labels_treemap.png ├── latest_project_pipeline.png ├── gitlab_merge_requests_gauge.png └── gitlab_project_contributors.png ├── commitlint.config.js ├── test ├── .eslintrc.yml └── components │ ├── __mocks__ │ └── react-measure.js │ ├── labels │ ├── LabelsBubble.test.js │ ├── LabelsTreemap.test.js │ ├── sampleData.js │ └── __snapshots__ │ │ ├── LabelsTreemap.test.js.snap │ │ └── LabelsBubble.test.js.snap │ ├── Branches.test.js │ └── __snapshots__ │ └── Branches.test.js.snap ├── .gitignore ├── .prettierrc.yml ├── CHANGELOG.md ├── src ├── components │ ├── labels │ │ ├── index.js │ │ ├── counts.js │ │ ├── LabelsBubble.js │ │ ├── LabelsTreemap.js │ │ ├── LabelsPie.js │ │ └── LabelsChart.js │ ├── index.js │ ├── activity │ │ ├── EventDescription.js │ │ ├── ProjectActivityItem.js │ │ ├── propTypes.js │ │ ├── EventTargetLink.js │ │ └── ProjectActivity.js │ ├── ProjectContributorsItem.js │ ├── ProjectMembersItem.js │ ├── JobHistory.js │ ├── JobHistoryItem.js │ ├── Branches.js │ ├── ProjectMembers.js │ ├── BranchesItem.js │ ├── ProjectContributors.js │ ├── ProjectMilestones.js │ ├── MergeRequestsGauge.js │ ├── JobHistogram.js │ ├── Project.js │ └── pipelines │ │ └── LatestProjectPipeline.js └── client │ ├── config.js │ ├── index.js │ └── gitlab.js ├── .lintstagedrc ├── .travis.yml ├── fixtures ├── index.js ├── project.json ├── milestones.json ├── branches.json └── project_events.json ├── .eslintrc.yml ├── .npmignore ├── LICENSE.md ├── .github └── ISSUE_TEMPLATE.md ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | *.snap -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/client') 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@mozaik/babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /preview/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/project.png -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | } 4 | -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | globals: 2 | test: true 3 | expect: true 4 | describe: true 5 | it: true 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *.log* 4 | /.nyc_output 5 | /coverage 6 | /lib 7 | /es 8 | /node_modules -------------------------------------------------------------------------------- /preview/gitlab_branches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/gitlab_branches.png -------------------------------------------------------------------------------- /preview/project_members.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/project_members.png -------------------------------------------------------------------------------- /preview/gitlab_labels_pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/gitlab_labels_pie.png -------------------------------------------------------------------------------- /preview/sample_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/sample_dashboard.png -------------------------------------------------------------------------------- /preview/gitlab_build_history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/gitlab_build_history.png -------------------------------------------------------------------------------- /preview/gitlab_labels_bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/gitlab_labels_bubble.png -------------------------------------------------------------------------------- /preview/gitlab_build_histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/gitlab_build_histogram.png -------------------------------------------------------------------------------- /preview/gitlab_labels_treemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/gitlab_labels_treemap.png -------------------------------------------------------------------------------- /preview/latest_project_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/latest_project_pipeline.png -------------------------------------------------------------------------------- /preview/gitlab_merge_requests_gauge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/gitlab_merge_requests_gauge.png -------------------------------------------------------------------------------- /preview/gitlab_project_contributors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/HEAD/preview/gitlab_project_contributors.png -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | printWidth: 100 2 | semi: false 3 | tabWidth: 4 4 | singleQuote: true 5 | bracketSpacing: true 6 | trailingComma: es5 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # mozaik-ext-gitlab Changelog 2 | 3 | > **Tags:** 4 | > - [New Feature] 5 | > - [Bug Fix] 6 | > - [Breaking Change] 7 | > - [Documentation] 8 | > - [Internal] 9 | > - [Polish] 10 | -------------------------------------------------------------------------------- /src/components/labels/index.js: -------------------------------------------------------------------------------- 1 | export { default as LabelsBubble } from './LabelsBubble' 2 | export { default as LabelsPie } from './LabelsPie' 3 | export { default as LabelsTreemap } from './LabelsTreemap' 4 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.js": [ 3 | "eslint --fix ./src/** ./test/**", 4 | "prettier --color --write \"{src,test}/**/*.js\" 'client.js'", 5 | "git add", 6 | "jest --bail --findRelatedTests" 7 | ] 8 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | - '10' 5 | script: 6 | - yarn run lint 7 | - yarn run fmt:check 8 | - yarn run test:cover 9 | after_success: 10 | - yarn run coverage 11 | -------------------------------------------------------------------------------- /fixtures/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | branches: require('./branches'), 5 | milestones: require('./milestones'), 6 | project: require('./project'), 7 | projectEvents: require('./project_events'), 8 | } 9 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parser: babel-eslint 2 | parserOptions: 3 | ecmaVersion: 6 4 | sourceType: module 5 | ecmaFeatures: 6 | jsx: true 7 | env: 8 | browser: true 9 | node: true 10 | es6: true 11 | extends: 12 | - eslint:recommended 13 | - plugin:react/recommended 14 | -------------------------------------------------------------------------------- /src/components/labels/counts.js: -------------------------------------------------------------------------------- 1 | export const labelByCountType = { 2 | open_issues_count: 'open issues', 3 | closed_issues_count: 'closed issues', 4 | open_merge_requests_count: 'opened merge requests', 5 | } 6 | 7 | export const countTypes = Object.keys(labelByCountType) 8 | 9 | export const countLabel = countType => labelByCountType[countType] 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Doc 2 | /preview 3 | 4 | # config 5 | .babelrc 6 | .eslintignore 7 | .eslintrc.yml 8 | .gitignore 9 | .lintstagedrc 10 | .npmignore 11 | .prettierignore 12 | .prettierrc.yml 13 | .travis.yml 14 | commitlint.config.js 15 | 16 | # Logs 17 | *.log* 18 | 19 | # Non transpiled source code 20 | /src 21 | 22 | # Tests 23 | /test 24 | /.nyc_output 25 | /coverage 26 | 27 | # GitHub 28 | /.github 29 | 30 | # OSX 31 | .DS_Store 32 | 33 | # IDE 34 | .idea -------------------------------------------------------------------------------- /src/client/config.js: -------------------------------------------------------------------------------- 1 | const convict = require('convict') 2 | 3 | const config = convict({ 4 | gitlab: { 5 | baseUrl: { 6 | doc: 'The gitlab API base url.', 7 | default: null, 8 | format: String, 9 | env: 'GITLAB_BASE_URL', 10 | }, 11 | token: { 12 | doc: 'The gitlab API token.', 13 | default: null, 14 | format: String, 15 | env: 'GITLAB_API_TOKEN', 16 | }, 17 | }, 18 | }) 19 | 20 | module.exports = config 21 | -------------------------------------------------------------------------------- /test/components/__mocks__/react-measure.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | export default class MockedMeasure extends Component { 5 | static propTypes = { 6 | onResize: PropTypes.func.isRequired, 7 | children: PropTypes.func.isRequired, 8 | } 9 | 10 | componentDidMount() { 11 | const { onResize } = this.props 12 | onResize({ 13 | bounds: { 14 | width: 600, 15 | height: 400, 16 | }, 17 | }) 18 | } 19 | 20 | render() { 21 | const { children } = this.props 22 | 23 | return children({ measureRef: () => {} }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Project from './Project' 2 | import ProjectMembers from './ProjectMembers' 3 | import ProjectContributors from './ProjectContributors' 4 | import JobHistory from './JobHistory' 5 | import JobHistogram from './JobHistogram' 6 | import Branches from './Branches' 7 | import ProjectActivity from './activity/ProjectActivity' 8 | import ProjectMilestones from './ProjectMilestones' 9 | import LatestProjectPipeline from './pipelines/LatestProjectPipeline' 10 | import * as labels from './labels' 11 | 12 | export default { 13 | Project, 14 | ProjectMembers, 15 | ProjectContributors, 16 | JobHistory, 17 | JobHistogram, 18 | Branches, 19 | ProjectActivity, 20 | ProjectMilestones, 21 | LatestProjectPipeline, 22 | ...labels, 23 | } 24 | -------------------------------------------------------------------------------- /test/components/labels/LabelsBubble.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import renderer from 'react-test-renderer' 3 | import { ThemeProvider } from 'styled-components' 4 | import { defaultTheme } from '@mozaik/ui' 5 | import LabelsBubble from './../../../src/components/labels/LabelsBubble' 6 | import { sampleProject, sampleLabels } from './sampleData' 7 | 8 | test('should render as expected', () => { 9 | const tree = renderer.create( 10 | 11 | 16 | 17 | ) 18 | 19 | expect(tree).toMatchSnapshot() 20 | }) 21 | -------------------------------------------------------------------------------- /test/components/labels/LabelsTreemap.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import renderer from 'react-test-renderer' 3 | import { ThemeProvider } from 'styled-components' 4 | import { defaultTheme } from '@mozaik/ui' 5 | import LabelsTreemap from './../../../src/components/labels/LabelsTreemap' 6 | import { sampleProject, sampleLabels } from './sampleData' 7 | 8 | test('should render as expected', () => { 9 | const tree = renderer.create( 10 | 11 | 16 | 17 | ) 18 | 19 | expect(tree).toMatchSnapshot() 20 | }) 21 | -------------------------------------------------------------------------------- /test/components/labels/sampleData.js: -------------------------------------------------------------------------------- 1 | export const sampleProject = { 2 | name: 'ploucifier', 3 | web_url: 'https://gitlab.com/plouc/ploucifier', 4 | } 5 | 6 | export const sampleLabels = [ 7 | { 8 | name: 'label A', 9 | id: 1, 10 | color: '#F00', 11 | open_issues_count: 21, 12 | closed_issues_count: 7, 13 | open_merge_requests_count: 12, 14 | }, 15 | { 16 | name: 'label B', 17 | id: 2, 18 | color: '#F0F', 19 | open_issues_count: 15, 20 | closed_issues_count: 13, 21 | open_merge_requests_count: 9, 22 | }, 23 | { 24 | name: 'label C', 25 | id: 3, 26 | color: '#00F', 27 | open_issues_count: 11, 28 | closed_issues_count: 20, 29 | open_merge_requests_count: 3, 30 | }, 31 | ] 32 | -------------------------------------------------------------------------------- /src/components/activity/EventDescription.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { truncate as trunc } from 'lodash' 3 | import { eventPropType } from './propTypes' 4 | 5 | const truncate = str => trunc(str, { length: 84 }) 6 | 7 | export default class EventDescription extends Component { 8 | static propTypes = eventPropType 9 | 10 | render() { 11 | const { target_type, target_title, note, push_data } = this.props 12 | 13 | if (target_type === 'Note' || target_type === 'DiffNote') { 14 | return
{truncate(note.body)}
15 | } 16 | 17 | if (target_type === 'Issue' || target_type === 'MergeRequest') { 18 | return
{truncate(target_title)}
19 | } 20 | 21 | if (push_data !== undefined) { 22 | return
{truncate(push_data.commit_title)}
23 | } 24 | 25 | return null 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Raphaël Benitte 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /src/components/activity/ProjectActivityItem.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react' 2 | import { WidgetListItem, WidgetAvatar } from '@mozaik/ui' 3 | import { eventPropType } from './propTypes' 4 | import EventTargetLink from './EventTargetLink' 5 | import EventDescription from './EventDescription' 6 | 7 | export default class ProjectActivityItem extends Component { 8 | static propTypes = eventPropType 9 | 10 | render() { 11 | const { action_name, author } = this.props 12 | 13 | return ( 14 | 17 | {author.name} {action_name}{' '} 18 | 19 | 20 | } 21 | meta={} 22 | pre={ 23 | 24 | {author.name} 25 | 26 | } 27 | align="top" 28 | /> 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | item | info | notes 2 | ------------------------------|------------|--------------------------------------------------------------------------------------------- 3 | **node** version | | *output from `node --version`* 4 | **npm** version | | *output from `npm --version`* 5 | **mozaik-ext-github** version | | *available in project's `package.json`* 6 | **mozaik** version | | *available in project's `package.json`* 7 | **mozaik-demo** version | | *version of the demo used, depends on which method you used to setup your Mozaïk dashboard* 8 | **component** | | *name of the extension's component or `client` if it's related to the extension's client* 9 | **browser** | | *browser used, applyable if the issue is not related to the client* 10 | 11 | ## Expected behavior 12 | 13 | *Description of the expected behavior.* 14 | 15 | ## Actual behavior 16 | 17 | *Description of the actual behavior.* 18 | 19 | ## Steps to reproduce 20 | 21 | *Description of the various steps required to reproduce the error.* 22 | -------------------------------------------------------------------------------- /src/components/ProjectContributorsItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { WidgetListItem } from '@mozaik/ui' 4 | import styled from 'styled-components' 5 | 6 | const Additions = styled.span` 7 | color: ${props => props.theme.colors.success}; 8 | ` 9 | 10 | const Deletions = styled.span` 11 | color: ${props => props.theme.colors.failure}; 12 | ` 13 | 14 | export default class ProjectContributorsItem extends Component { 15 | static propTypes = { 16 | contributor: PropTypes.shape({ 17 | name: PropTypes.string.isRequired, 18 | commits: PropTypes.number.isRequired, 19 | }).isRequired, 20 | } 21 | 22 | render() { 23 | const { 24 | contributor: { name, commits, additions, deletions }, 25 | } = this.props 26 | 27 | return ( 28 | 32 | {commits} commit {additions} ++{' '} 33 | {deletions} -- 34 | 35 | } 36 | /> 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/ProjectMembersItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { WidgetListItem, WidgetAvatar, ExternalLink } from '@mozaik/ui' 4 | 5 | export default class ProjectMembersItem extends Component { 6 | static propTypes = { 7 | member: PropTypes.shape({ 8 | name: PropTypes.string.isRequired, 9 | username: PropTypes.string.isRequired, 10 | avatar_url: PropTypes.string, 11 | web_url: PropTypes.string.isRequired, 12 | state: PropTypes.string.isRequired, 13 | }).isRequired, 14 | } 15 | 16 | render() { 17 | const { 18 | member: { name, username, avatar_url, web_url, state }, 19 | } = this.props 20 | 21 | let avatar = null 22 | if (avatar_url) { 23 | avatar = ( 24 | 25 | {name} 26 | 27 | ) 28 | } 29 | 30 | return ( 31 | {name}} 33 | href={web_url} 34 | pre={avatar} 35 | meta={`@${username} - ${state}`} 36 | align="top" 37 | /> 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/activity/propTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | 3 | export const eventPropType = { 4 | project_id: PropTypes.number.isRequired, 5 | action_name: PropTypes.string.isRequired, 6 | target_type: PropTypes.oneOf([ 7 | 'Issue', 8 | 'Milestone', 9 | 'MergeRequest', 10 | 'Note', 11 | 'Project', 12 | 'Snippet', 13 | 'User', 14 | 'DiscussionNote', 15 | 'DiffNote', 16 | ]), 17 | target_id: PropTypes.number, 18 | target_iid: PropTypes.number, 19 | target_title: PropTypes.string, 20 | created_at: PropTypes.string.isRequired, 21 | author: PropTypes.shape({ 22 | avatar_url: PropTypes.string.isRequired, 23 | name: PropTypes.string.isRequired, 24 | web_url: PropTypes.string.isRequired, 25 | }).isRequired, 26 | note: PropTypes.shape({ 27 | noteable_type: PropTypes.oneOf(['Issue', 'MergeRequest']).isRequired, 28 | }), 29 | push_data: PropTypes.shape({ 30 | action: PropTypes.string.isRequired, 31 | commit_count: PropTypes.number.isRequired, 32 | commit_from: PropTypes.string.isRequired, 33 | commit_to: PropTypes.string.isRequired, 34 | commit_title: PropTypes.string.isRequired, 35 | ref_type: PropTypes.string.isRequired, 36 | ref: PropTypes.string.isRequired, 37 | }), 38 | } 39 | -------------------------------------------------------------------------------- /src/components/labels/LabelsBubble.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { ResponsiveBubble } from 'nivo' 3 | import LabelsChart from './LabelsChart' 4 | 5 | const margin = { 6 | top: 10, 7 | right: 10, 8 | bottom: 10, 9 | left: 10, 10 | } 11 | 12 | export default class LabelsBubble extends Component { 13 | static getApiRequest = LabelsChart.getApiRequest 14 | 15 | render() { 16 | return ( 17 | 18 | {({ labels, countBy, animate }) => { 19 | const data = { 20 | name: 'labels', 21 | color: '#000', 22 | children: labels, 23 | } 24 | 25 | return ( 26 | `${d.name} ${d[countBy]}`} 32 | labelTextColor="inherit:darker(1.6)" 33 | leavesOnly={true} 34 | colorBy={d => d.color} 35 | animate={animate} 36 | /> 37 | ) 38 | }} 39 | 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/labels/LabelsTreemap.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { ResponsiveTreeMap } from 'nivo' 3 | import LabelsChart from './LabelsChart' 4 | 5 | export default class LabelsTreemap extends Component { 6 | static getApiRequest = LabelsChart.getApiRequest 7 | 8 | render() { 9 | return ( 10 | 11 | {({ labels, countBy, animate }) => { 12 | const data = { 13 | name: 'labels', 14 | color: '#000', 15 | children: labels, 16 | } 17 | 18 | return ( 19 | `${d.name} ${d[countBy]}`} 26 | labelTextColor="inherit:darker(1.6)" 27 | leavesOnly={true} 28 | outerPadding={10} 29 | innerPadding={2} 30 | colorBy={d => d.color} 31 | animate={animate} 32 | /> 33 | ) 34 | }} 35 | 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/labels/LabelsPie.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { ResponsivePie } from 'nivo' 3 | import LabelsChart from './LabelsChart' 4 | 5 | const margin = { 6 | top: 30, 7 | right: 60, 8 | bottom: 30, 9 | left: 60, 10 | } 11 | 12 | const skipAngle = 5 13 | 14 | export default class LabelsPie extends Component { 15 | static getApiRequest = LabelsChart.getApiRequest 16 | 17 | render() { 18 | return ( 19 | 20 | {({ labels, countBy, animate, theme }) => { 21 | const data = labels.map(label => ({ 22 | ...label, 23 | id: `${label.id}`, 24 | label: label.name, 25 | value: label[countBy], 26 | })) 27 | 28 | return ( 29 | d.color} 33 | innerRadius={0.6} 34 | padAngle={1} 35 | radialLabel="name" 36 | radialLabelsSkipAngle={skipAngle} 37 | slicesLabel={countBy} 38 | slicesLabelsSkipAngle={skipAngle} 39 | slicesLabelsTextColor="inherit:darker(1.6)" 40 | animate={animate} 41 | theme={theme} 42 | /> 43 | ) 44 | }} 45 | 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/activity/EventTargetLink.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { eventPropType } from './propTypes' 3 | 4 | export default class EventTargetLink extends Component { 5 | static propTypes = eventPropType 6 | 7 | render() { 8 | const { target_type, target_id, target_iid, note, push_data } = this.props 9 | 10 | if (target_type === 'Issue') { 11 | return ( 12 | 13 | {target_type} #{target_iid} 14 | 15 | ) 16 | } 17 | 18 | if (target_type === 'MergeRequest') { 19 | return ( 20 | 21 | {target_type} !{target_iid} 22 | 23 | ) 24 | } 25 | 26 | if (target_type === 'Note' || target_type === 'DiffNote') { 27 | if (note.noteable_type === 'Issue') { 28 | return ( 29 | 30 | {note.noteable_type} #{note.noteable_iid} 31 | 32 | ) 33 | } 34 | 35 | if (note.noteable_type === 'MergeRequest') { 36 | return ( 37 | 38 | {note.noteable_type} !{note.noteable_iid} 39 | 40 | ) 41 | } 42 | } 43 | 44 | if (push_data !== undefined) { 45 | return ( 46 | 47 | {push_data.ref_type} {push_data.ref} 48 | 49 | ) 50 | } 51 | 52 | return ( 53 | 54 | {target_type} {target_id} {target_iid} 55 | 56 | ) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/JobHistory.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { 4 | TrapApiError, 5 | Widget, 6 | WidgetHeader, 7 | WidgetBody, 8 | WidgetLoader, 9 | ExternalLink, 10 | BarChartIcon, 11 | } from '@mozaik/ui' 12 | import JobHistoryItem from './JobHistoryItem' 13 | 14 | export default class JobHistory extends Component { 15 | static propTypes = { 16 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 17 | title: PropTypes.string, 18 | apiData: PropTypes.shape({ 19 | project: PropTypes.shape({ 20 | name: PropTypes.string.isRequired, 21 | web_url: PropTypes.string.isRequired, 22 | }).isRequired, 23 | jobs: PropTypes.shape({ 24 | items: PropTypes.array.isRequired, 25 | }).isRequired, 26 | }), 27 | apiError: PropTypes.object, 28 | } 29 | 30 | static getApiRequest({ project }) { 31 | return { 32 | id: `gitlab.projectJobs.${project}`, 33 | params: { project }, 34 | } 35 | } 36 | 37 | render() { 38 | const { title, apiData, apiError } = this.props 39 | 40 | let body = 41 | let subject = null 42 | if (apiData) { 43 | const { project, jobs } = apiData 44 | 45 | subject = {project.name} 46 | 47 | body = ( 48 |
49 | {jobs.items.map(job => ( 50 | 51 | ))} 52 |
53 | ) 54 | } 55 | 56 | return ( 57 | 58 | 63 | 64 | {body} 65 | 66 | 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/JobHistoryItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import moment from 'moment' 4 | import { WidgetListItem, WidgetLabel, WidgetStatusChip, ClockIcon, ExternalLink } from '@mozaik/ui' 5 | 6 | export default class JobHistoryItem extends Component { 7 | static propTypes = { 8 | project: PropTypes.shape({ 9 | web_url: PropTypes.string.isRequired, 10 | }).isRequired, 11 | job: PropTypes.shape({ 12 | id: PropTypes.number.isRequired, 13 | status: PropTypes.string.isRequired, 14 | finished_at: PropTypes.string, 15 | commit: PropTypes.shape({ 16 | message: PropTypes.string.isRequired, 17 | }), 18 | }).isRequired, 19 | } 20 | 21 | render() { 22 | const { project, job } = this.props 23 | 24 | return ( 25 |
26 | 29 | 33 | #{job.id} 34 | 35 |   36 | 37 |   38 | 39 |   40 | {job.commit && {job.commit.message}} 41 | 42 | } 43 | meta={ 44 | job.finished_at && ( 45 | 55 | ) 56 | } 57 | pre={} 58 | /> 59 |
60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /fixtures/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 250833, 3 | "description": "GitLab Runner", 4 | "name": "gitlab-runner", 5 | "name_with_namespace": "GitLab.org / gitlab-runner", 6 | "path": "gitlab-runner", 7 | "path_with_namespace": "gitlab-org/gitlab-runner", 8 | "created_at": "2015-04-27T21:10:25.322Z", 9 | "default_branch": "master", 10 | "tag_list": [], 11 | "ssh_url_to_repo": "git@gitlab.com:gitlab-org/gitlab-runner.git", 12 | "http_url_to_repo": "https://gitlab.com/gitlab-org/gitlab-runner.git", 13 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner", 14 | "readme_url": "https://gitlab.com/gitlab-org/gitlab-runner/blob/master/README.md", 15 | "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/project/avatar/250833/runner_logo.png", 16 | "star_count": 973, 17 | "forks_count": 762, 18 | "last_activity_at": "2018-07-27T22:14:44.591Z", 19 | "_links": { 20 | "self": "https://gitlab.com/api/v4/projects/250833", 21 | "issues": "https://gitlab.com/api/v4/projects/250833/issues", 22 | "merge_requests": "https://gitlab.com/api/v4/projects/250833/merge_requests", 23 | "repo_branches": "https://gitlab.com/api/v4/projects/250833/repository/branches", 24 | "labels": "https://gitlab.com/api/v4/projects/250833/labels", 25 | "events": "https://gitlab.com/api/v4/projects/250833/events", 26 | "members": "https://gitlab.com/api/v4/projects/250833/members" 27 | }, 28 | "archived": false, 29 | "visibility": "public", 30 | "resolve_outdated_diff_discussions": false, 31 | "container_registry_enabled": true, 32 | "issues_enabled": true, 33 | "merge_requests_enabled": true, 34 | "wiki_enabled": false, 35 | "jobs_enabled": true, 36 | "snippets_enabled": false, 37 | "shared_runners_enabled": true, 38 | "lfs_enabled": true, 39 | "creator_id": 12452, 40 | "namespace": { 41 | "id": 9970, 42 | "name": "GitLab.org", 43 | "path": "gitlab-org", 44 | "kind": "group", 45 | "full_path": "gitlab-org", 46 | "parent_id": null 47 | }, 48 | "import_status": "finished", 49 | "open_issues_count": 675, 50 | "public_jobs": true, 51 | "ci_config_path": null, 52 | "shared_with_groups": [], 53 | "only_allow_merge_if_pipeline_succeeds": true, 54 | "request_access_enabled": false, 55 | "only_allow_merge_if_all_discussions_are_resolved": false, 56 | "printing_merge_request_link_enabled": true, 57 | "merge_method": "merge", 58 | "permissions": { 59 | "project_access": null, 60 | "group_access": null 61 | }, 62 | "approvals_before_merge": 1 63 | } -------------------------------------------------------------------------------- /src/components/activity/ProjectActivity.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { 4 | TrapApiError, 5 | Widget, 6 | WidgetHeader, 7 | WidgetBody, 8 | WidgetLoader, 9 | ExternalLink, 10 | ActivityIcon, 11 | } from '@mozaik/ui' 12 | import ProjectActivityItem from './ProjectActivityItem' 13 | import { eventPropType } from './propTypes' 14 | 15 | export default class ProjectActivity extends Component { 16 | static propTypes = { 17 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 18 | title: PropTypes.string, 19 | apiData: PropTypes.shape({ 20 | project: PropTypes.shape({ 21 | name: PropTypes.string.isRequired, 22 | web_url: PropTypes.string.isRequired, 23 | }).isRequired, 24 | events: PropTypes.shape({ 25 | items: PropTypes.arrayOf(PropTypes.shape(eventPropType)).isRequired, 26 | pagination: PropTypes.shape({ 27 | total: PropTypes.number.isRequired, 28 | }).isRequired, 29 | }).isRequired, 30 | }), 31 | apiError: PropTypes.object, 32 | } 33 | 34 | static getApiRequest({ project }) { 35 | return { 36 | id: `gitlab.projectEvents.${project}`, 37 | params: { project }, 38 | } 39 | } 40 | 41 | render() { 42 | const { title, apiData, apiError } = this.props 43 | 44 | let body = 45 | let subject = null 46 | if (apiData) { 47 | const { project, events } = apiData 48 | 49 | subject = {project.name} 50 | 51 | body = ( 52 | 53 | {events.items.map((event, i) => ( 54 | 55 | ))} 56 | 57 | ) 58 | } 59 | 60 | return ( 61 | 62 | 67 | 68 | {body} 69 | 70 | 71 | ) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/Branches.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { 4 | TrapApiError, 5 | Widget, 6 | WidgetHeader, 7 | WidgetBody, 8 | WidgetLoader, 9 | ExternalLink, 10 | GitBranchIcon, 11 | } from '@mozaik/ui' 12 | import BranchesItem from './BranchesItem' 13 | 14 | export default class Branches extends Component { 15 | static propTypes = { 16 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 17 | title: PropTypes.string, 18 | apiData: PropTypes.shape({ 19 | project: PropTypes.shape({ 20 | name: PropTypes.string.isRequired, 21 | web_url: PropTypes.string.isRequired, 22 | }).isRequired, 23 | branches: PropTypes.shape({ 24 | items: PropTypes.array.isRequired, 25 | pagination: PropTypes.shape({ 26 | total: PropTypes.number.isRequired, 27 | }).isRequired, 28 | }).isRequired, 29 | }), 30 | apiError: PropTypes.object, 31 | } 32 | 33 | static getApiRequest({ project }) { 34 | return { 35 | id: `gitlab.projectBranches.${project}`, 36 | params: { project }, 37 | } 38 | } 39 | 40 | render() { 41 | const { title, apiData, apiError } = this.props 42 | 43 | let body = 44 | let subject = null 45 | let count = 0 46 | if (apiData) { 47 | const { project, branches } = apiData 48 | 49 | count = branches.pagination.total 50 | 51 | subject = {project.name} 52 | 53 | body = ( 54 | 55 | {branches.items.map(branch => ( 56 | 57 | ))} 58 | 59 | ) 60 | } 61 | 62 | return ( 63 | 64 | 70 | 71 | {body} 72 | 73 | 74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/components/ProjectMembers.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { 4 | TrapApiError, 5 | Widget, 6 | WidgetHeader, 7 | WidgetBody, 8 | WidgetLoader, 9 | ExternalLink, 10 | UsersIcon, 11 | } from '@mozaik/ui' 12 | import ProjectMembersItem from './ProjectMembersItem' 13 | 14 | export default class ProjectMembers extends Component { 15 | static propTypes = { 16 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 17 | title: PropTypes.string, 18 | apiData: PropTypes.shape({ 19 | project: PropTypes.shape({ 20 | name: PropTypes.string.isRequired, 21 | web_url: PropTypes.string.isRequired, 22 | }).isRequired, 23 | members: PropTypes.shape({ 24 | items: PropTypes.array.isRequired, 25 | pagination: PropTypes.shape({ 26 | total: PropTypes.number.isRequired, 27 | }).isRequired, 28 | }).isRequired, 29 | }), 30 | apiError: PropTypes.object, 31 | } 32 | 33 | static getApiRequest({ project }) { 34 | return { 35 | id: `gitlab.projectMembers.${project}`, 36 | params: { project }, 37 | } 38 | } 39 | 40 | render() { 41 | const { title, apiData, apiError } = this.props 42 | 43 | let body = 44 | let subject = null 45 | let count 46 | if (apiData) { 47 | const { project, members } = apiData 48 | 49 | count = members.pagination.total 50 | 51 | subject = {project.name} 52 | 53 | body = ( 54 | 55 | {members.items.map(member => ( 56 | 57 | ))} 58 | 59 | ) 60 | } 61 | 62 | return ( 63 | 64 | 70 | 71 | {body} 72 | 73 | 74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/components/BranchesItem.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import moment from 'moment' 4 | import styled from 'styled-components' 5 | import { WidgetListItem, Text, typography, ClockIcon, ExternalLink } from '@mozaik/ui' 6 | 7 | const Header = styled.div` 8 | display: flex; 9 | justify-content: space-between; 10 | ` 11 | 12 | const CommitSha = styled.a` 13 | ${props => typography(props.theme, 'mono', 'small')}; 14 | ` 15 | 16 | export default class BranchesItem extends Component { 17 | static propTypes = { 18 | project: PropTypes.shape({ 19 | web_url: PropTypes.string.isRequired, 20 | }).isRequired, 21 | branch: PropTypes.shape({ 22 | name: PropTypes.string.isRequired, 23 | protected: PropTypes.bool.isRequired, 24 | commit: PropTypes.shape({ 25 | id: PropTypes.string.isRequired, 26 | message: PropTypes.string.isRequired, 27 | author_name: PropTypes.string.isRequired, 28 | committed_date: PropTypes.string.isRequired, 29 | }).isRequired, 30 | }).isRequired, 31 | } 32 | 33 | render() { 34 | const { project, branch } = this.props 35 | 36 | return ( 37 | 40 | 41 | {branch.name} 42 | 43 |   44 | 49 | {branch.commit.id.substring(0, 7)} 50 | 51 | 52 | } 53 | meta={ 54 | 55 | {branch.commit.message} 56 | 63 | 64 |   65 | {moment(branch.commit.committed_date).fromNow()} 66 | 67 | 68 | } 69 | /> 70 | ) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/ProjectContributors.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import { 5 | TrapApiError, 6 | Widget, 7 | WidgetHeader, 8 | WidgetBody, 9 | WidgetLoader, 10 | ExternalLink, 11 | UsersIcon, 12 | } from '@mozaik/ui' 13 | import ProjectContributorsItem from './ProjectContributorsItem' 14 | 15 | export default class ProjectContributors extends Component { 16 | static propTypes = { 17 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 18 | title: PropTypes.string, 19 | apiData: PropTypes.shape({ 20 | project: PropTypes.shape({ 21 | name: PropTypes.string.isRequired, 22 | web_url: PropTypes.string.isRequired, 23 | }).isRequired, 24 | contributors: PropTypes.shape({ 25 | items: PropTypes.array.isRequired, 26 | pagination: PropTypes.shape({ 27 | total: PropTypes.number.isRequired, 28 | }).isRequired, 29 | }).isRequired, 30 | }), 31 | apiError: PropTypes.object, 32 | } 33 | 34 | static getApiRequest({ project }) { 35 | return { 36 | id: `gitlab.projectContributors.${project}`, 37 | params: { project }, 38 | } 39 | } 40 | 41 | render() { 42 | const { title, apiData, apiError } = this.props 43 | 44 | let body = 45 | let subject = null 46 | let count 47 | if (apiData) { 48 | const { project, contributors } = apiData 49 | 50 | const sortedContributors = _.orderBy(contributors.items.slice(), ['commits'], ['desc']) 51 | 52 | count = contributors.pagination.total 53 | 54 | subject = {project.name} 55 | 56 | body = ( 57 | 58 | {sortedContributors.map(contributor => ( 59 | 63 | ))} 64 | 65 | ) 66 | } 67 | 68 | return ( 69 | 70 | 76 | 77 | {body} 78 | 79 | 80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/components/ProjectMilestones.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { 4 | TrapApiError, 5 | Widget, 6 | WidgetHeader, 7 | WidgetBody, 8 | WidgetLoader, 9 | WidgetListItem, 10 | ExternalLink, 11 | } from '@mozaik/ui' 12 | 13 | export default class ProjectMilestones extends Component { 14 | static propTypes = { 15 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 16 | title: PropTypes.string, 17 | apiData: PropTypes.shape({ 18 | project: PropTypes.object.isRequired, 19 | milestones: { 20 | items: PropTypes.arrayOf( 21 | PropTypes.shape({ 22 | id: PropTypes.number.isRequired, 23 | title: PropTypes.string.isRequired, 24 | state: PropTypes.string.isRequired, 25 | due_date: PropTypes.string.isRequired, 26 | }) 27 | ).isRequired, 28 | pagination: PropTypes.shape({ 29 | total: PropTypes.number.isRequired, 30 | }).isRequired, 31 | }, 32 | }), 33 | apiError: PropTypes.object, 34 | } 35 | 36 | static getApiRequest({ project }) { 37 | return { 38 | id: `gitlab.projectMilestones.${project}`, 39 | params: { project }, 40 | } 41 | } 42 | 43 | render() { 44 | const { title, apiData, apiError } = this.props 45 | 46 | let body = 47 | let subject = null 48 | let count 49 | if (apiData) { 50 | const { project, milestones } = apiData 51 | 52 | count = milestones.pagination.total 53 | 54 | subject = {project.name} 55 | 56 | body = ( 57 | 58 | {milestones.items.map(milestone => ( 59 | 65 | ))} 66 | 67 | ) 68 | } 69 | 70 | return ( 71 | 72 | 77 | 78 | {body} 79 | 80 | 81 | ) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/components/labels/LabelsChart.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { 4 | TrapApiError, 5 | Widget, 6 | WidgetHeader, 7 | WidgetBody, 8 | WidgetLoader, 9 | ExternalLink, 10 | TagIcon, 11 | } from '@mozaik/ui' 12 | import { countTypes, countLabel } from './counts' 13 | 14 | export default class LabelsChart extends Component { 15 | static propTypes = { 16 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 17 | countBy: PropTypes.oneOf(countTypes).isRequired, 18 | apiData: PropTypes.shape({ 19 | project: PropTypes.shape({ 20 | name: PropTypes.string.isRequired, 21 | web_url: PropTypes.string.isRequired, 22 | }).isRequired, 23 | labels: PropTypes.arrayOf( 24 | PropTypes.shape({ 25 | id: PropTypes.number.isRequired, 26 | name: PropTypes.string.isRequired, 27 | color: PropTypes.string.isRequired, 28 | open_issues_count: PropTypes.number.isRequired, 29 | closed_issues_count: PropTypes.number.isRequired, 30 | open_merge_requests_count: PropTypes.number.isRequired, 31 | }) 32 | ).isRequired, 33 | }), 34 | apiError: PropTypes.object, 35 | title: PropTypes.string, 36 | animate: PropTypes.bool.isRequired, 37 | children: PropTypes.func.isRequired, 38 | theme: PropTypes.object.isRequired, 39 | } 40 | 41 | static defaultProps = { 42 | countBy: 'open_issues_count', 43 | animate: false, 44 | } 45 | 46 | static getApiRequest({ project }) { 47 | return { 48 | id: `gitlab.projectLabels.${project}`, 49 | params: { project }, 50 | } 51 | } 52 | 53 | render() { 54 | const { apiData, apiError, title, countBy, animate, children, theme } = this.props 55 | 56 | let body = 57 | let subject = null 58 | let count = 0 59 | if (apiData) { 60 | const { project, labels } = apiData 61 | 62 | count = labels.length 63 | 64 | subject = {project.name} 65 | 66 | body = children({ 67 | labels, 68 | countBy, 69 | animate, 70 | theme: theme.charts, 71 | }) 72 | } 73 | 74 | return ( 75 | 76 | 82 | 83 | {body} 84 | 85 | 86 | ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mozaik/ext-gitlab", 3 | "version": "2.0.0-alpha.9", 4 | "description": "Mozaik GitLab widgets", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/plouc/mozaik-ext-gitlab" 8 | }, 9 | "license": "MIT", 10 | "author": { 11 | "name": "Raphaël Benitte", 12 | "url": "https://github.com/plouc" 13 | }, 14 | "homepage": "https://github.com/plouc/mozaik-ext-gitlab", 15 | "main": "./lib/components/index.js", 16 | "module": "es/components/index.js", 17 | "jsnext:main": "es/components/index.js", 18 | "keywords": [ 19 | "gitlab", 20 | "gitlab-ci", 21 | "mozaik", 22 | "widget", 23 | "extension", 24 | "dashboard" 25 | ], 26 | "engineStrict": true, 27 | "engines": { 28 | "node": ">=8.2.0", 29 | "npm": ">=3.0.0" 30 | }, 31 | "dependencies": { 32 | "chalk": "^2.0.1", 33 | "convict": "^4.0.0", 34 | "lodash": "^4.17.4", 35 | "lodash-es": "^4.17.4", 36 | "moment": "^2.18.1", 37 | "prop-types": "^15.5.10" 38 | }, 39 | "devDependencies": { 40 | "@commitlint/cli": "^7.0.0", 41 | "@commitlint/config-conventional": "^7.0.1", 42 | "@mozaik/babel-preset": "^1.0.0-alpha.6", 43 | "@mozaik/ui": "^2.0.0-rc.0", 44 | "babel-cli": "^6.24.1", 45 | "babel-eslint": "^7.2.3", 46 | "babel-jest": "^20.0.3", 47 | "coveralls": "^2.11.15", 48 | "cross-env": "^5.0.1", 49 | "enzyme": "^3.3.0", 50 | "enzyme-adapter-react-16": "^1.1.1", 51 | "eslint": "^5.2.0", 52 | "eslint-plugin-react": "^7.10.0", 53 | "husky": "^0.14.3", 54 | "jest": "^23.4.2", 55 | "lint-staged": "^7.2.0", 56 | "nivo": "^0.12.0", 57 | "nock": "^9.0.14", 58 | "prettier": "^1.14.0", 59 | "react": "^16.4.0", 60 | "react-dom": "^16.4.0", 61 | "react-test-renderer": "^16.4.0", 62 | "styled-components": "^2.1.1" 63 | }, 64 | "peerDependencies": { 65 | "@mozaik/ui": "^2.0.0-rc.0", 66 | "nivo": "^0.12.0", 67 | "react": "^16.4.0", 68 | "styled-components": "^2.1.1" 69 | }, 70 | "scripts": { 71 | "lint": "eslint ./src/** ./test/**", 72 | "lint:fix": "eslint --fix ./src/** ./test/**", 73 | "test": "jest --verbose", 74 | "test:cover": "jest --verbose --coverage", 75 | "coverage": "cat ./coverage/lcov.info | coveralls", 76 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib", 77 | "build:commonjs:watch": "npm run build:commonjs -- --watch", 78 | "build:es": "cross-env BABEL_ENV=es babel src --out-dir es", 79 | "build:es:watch": "npm run build:es -- --watch", 80 | "build": "npm run build:commonjs && npm run build:es", 81 | "fmt": "prettier --color --write \"{src,test}/**/*.js\" 'client.js'", 82 | "fmt:check": "prettier --list-different \"{src,test}/**/*.js\" 'client.js'", 83 | "prepublishOnly": "npm run lint && npm test && npm run build", 84 | "version": "echo ${npm_package_version}", 85 | "precommit": "lint-staged", 86 | "commitmsg": "commitlint -E GIT_PARAMS" 87 | }, 88 | "jest": { 89 | "testURL": "http://localhost/" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/MergeRequestsGauge.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import { Gauge, Widget, WidgetHeader, WidgetBody } from '@mozaik/ui' 5 | 6 | export default class MergeRequestsGauge extends Component { 7 | static propTypes = { 8 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 9 | thresholds: PropTypes.arrayOf( 10 | PropTypes.shape({ 11 | threshold: PropTypes.number.isRequired, 12 | color: PropTypes.string.isRequired, 13 | message: PropTypes.string.isRequired, 14 | }) 15 | ).isRequired, 16 | apiData: PropTypes.number.isRequired, 17 | apiError: PropTypes.object, 18 | } 19 | 20 | static defaultProps = { 21 | thresholds: [ 22 | { threshold: 3, color: '#85e985', message: 'good job!' }, 23 | { 24 | threshold: 5, 25 | color: '#ecc265', 26 | message: 'you should consider reviewing', 27 | }, 28 | { 29 | threshold: 10, 30 | color: '#f26a3f', 31 | message: 'merge requests overflow', 32 | }, 33 | ], 34 | apiData: 0, 35 | } 36 | 37 | static getApiRequest({ project }) { 38 | return { 39 | id: `gitlab.projectMergeRequests.${project}.opened`, 40 | params: { 41 | project, 42 | query: { 43 | state: 'opened', 44 | }, 45 | }, 46 | } 47 | } 48 | 49 | render() { 50 | const { thresholds, apiData: mergeRequestCount } = this.props 51 | 52 | const cappedValue = Math.min( 53 | mergeRequestCount, 54 | _.max(thresholds.map(threshold => threshold.threshold)) 55 | ) 56 | let message = null 57 | const normThresholds = thresholds.map(threshold => { 58 | if (message === null && cappedValue <= threshold.threshold) { 59 | message = threshold.message 60 | } 61 | 62 | return { 63 | upperBound: threshold.threshold, 64 | color: threshold.color, 65 | } 66 | }) 67 | 68 | return ( 69 | 70 | 75 | 76 |
77 | 83 |
84 |
{message}
85 |
86 |
87 | ) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/components/Branches.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Enzyme from 'enzyme' 3 | import Adapter from 'enzyme-adapter-react-16' 4 | import renderer from 'react-test-renderer' 5 | import { ThemeProvider } from 'styled-components' 6 | import { WidgetHeader, WidgetLoader, defaultTheme } from '@mozaik/ui' 7 | import Branches from './../../src/components/Branches' 8 | import fixtures from '../../fixtures' 9 | 10 | Enzyme.configure({ adapter: new Adapter() }) 11 | 12 | test('should return correct api request', () => { 13 | expect( 14 | Branches.getApiRequest({ 15 | project: fixtures.project.name, 16 | }) 17 | ).toEqual({ 18 | id: `gitlab.projectBranches.${fixtures.project.name}`, 19 | params: { project: fixtures.project.name }, 20 | }) 21 | }) 22 | 23 | test('should display loader if no apiData available', () => { 24 | const wrapper = Enzyme.shallow() 25 | 26 | expect(wrapper.find(WidgetLoader).exists()).toBeTruthy() 27 | }) 28 | 29 | test('header should display 0 count by default', () => { 30 | const wrapper = Enzyme.shallow() 31 | 32 | const header = wrapper.find(WidgetHeader) 33 | expect(header.prop('count')).toBe(0) 34 | }) 35 | 36 | test('header should display pull request count when api sent data', () => { 37 | const wrapper = Enzyme.shallow( 38 | 45 | ) 46 | 47 | const header = wrapper.find(WidgetHeader) 48 | expect(header.exists()).toBeTruthy() 49 | expect(header.prop('count')).toBe(42) 50 | }) 51 | 52 | test(`header title should default to ' Branches'`, () => { 53 | const wrapper = Enzyme.shallow( 54 | 61 | ) 62 | 63 | const header = wrapper.find(WidgetHeader) 64 | expect(header.prop('title')).toBe('Branches') 65 | }) 66 | 67 | test(`header title should be overridden when passing 'title' prop`, () => { 68 | const customTitle = 'Custom Title' 69 | const wrapper = Enzyme.shallow( 70 | 78 | ) 79 | 80 | const header = wrapper.find(WidgetHeader) 81 | expect(header.prop('title')).toBe(customTitle) 82 | }) 83 | 84 | test('should render as expected', () => { 85 | const tree = renderer.create( 86 | 87 | 94 | 95 | ) 96 | 97 | expect(tree).toMatchSnapshot() 98 | }) 99 | -------------------------------------------------------------------------------- /src/components/JobHistogram.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import moment from 'moment' 4 | import { 5 | TrapApiError, 6 | Widget, 7 | WidgetHeader, 8 | WidgetBody, 9 | WidgetLoader, 10 | ExternalLink, 11 | BarChartIcon, 12 | } from '@mozaik/ui' 13 | import { ResponsiveBar } from 'nivo' 14 | 15 | const margin = { top: 20, right: 20, bottom: 60, left: 70 } 16 | const colorBy = d => d.color 17 | const axisLeft = { 18 | tickPadding: 7, 19 | tickSize: 0, 20 | legend: 'duration (mn)', 21 | legendPosition: 'center', 22 | legendOffset: -40, 23 | } 24 | const axisBottom = { 25 | tickSize: 0, 26 | tickPadding: 7, 27 | legend: 'job id', 28 | legendPosition: 'center', 29 | legendOffset: 40, 30 | } 31 | 32 | export default class JobHistogram extends Component { 33 | static propTypes = { 34 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 35 | title: PropTypes.string, 36 | apiData: PropTypes.shape({ 37 | project: PropTypes.shape({ 38 | name: PropTypes.string.isRequired, 39 | web_url: PropTypes.string.isRequired, 40 | }).isRequired, 41 | jobs: PropTypes.shape({ 42 | items: PropTypes.array.isRequired, 43 | }).isRequired, 44 | }), 45 | apiError: PropTypes.object, 46 | theme: PropTypes.object.isRequired, 47 | } 48 | 49 | static getApiRequest({ project }) { 50 | return { 51 | id: `gitlab.projectJobs.${project}`, 52 | params: { project }, 53 | } 54 | } 55 | 56 | render() { 57 | const { title, apiData, apiError, theme } = this.props 58 | 59 | const getColorForStatus = status => { 60 | if (status === 'success') return theme.colors.success 61 | if (status === 'failed') return theme.colors.failure 62 | return theme.colors.unknown 63 | } 64 | 65 | let body = 66 | let subject = null 67 | if (apiData) { 68 | const { project, jobs } = apiData 69 | subject = {project.name} 70 | 71 | const data = [ 72 | { 73 | id: 'jobs', 74 | data: jobs.items.map(({ id, status, started_at, finished_at }) => { 75 | let duration = 0 76 | if (started_at && finished_at) { 77 | duration = moment(finished_at).diff(started_at, 'minutes') 78 | } 79 | 80 | return { 81 | id: `${id}`, 82 | duration, 83 | status, 84 | x: id, 85 | y: duration, 86 | color: getColorForStatus(status), 87 | } 88 | }), 89 | }, 90 | ] 91 | 92 | body = ( 93 | 104 | ) 105 | } 106 | 107 | return ( 108 | 109 | 114 | 115 | {body} 116 | 117 | 118 | ) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test/components/labels/__snapshots__/LabelsTreemap.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should render as expected 1`] = ` 4 |
8 |
11 |
15 | 16 | 27 | Labels by open issues 28 |
31 | 3 32 |
33 |
34 |
37 | 48 | 51 | 57 | 58 |
59 |
60 |
64 |
72 | 77 | 80 | 84 | 91 | 94 | 104 | label A 21 105 | 106 | 107 | 108 | 112 | 119 | 122 | 132 | label B 15 133 | 134 | 135 | 136 | 140 | 147 | 150 | 160 | label C 11 161 | 162 | 163 | 164 | 165 | 166 |
167 |
168 |
169 |
170 | `; 171 | -------------------------------------------------------------------------------- /src/client/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const config = require('./config') 4 | const Gitlab = require('./gitlab').Gitlab 5 | 6 | const aggregatePipelineJobs = jobs => { 7 | let stages = [] 8 | jobs.forEach(job => { 9 | let stage = stages.find(s => s.name === job.stage) 10 | if (stage === undefined) { 11 | stage = { 12 | name: job.stage, 13 | jobs: [], 14 | status: 'success', 15 | } 16 | stages.push(stage) 17 | } 18 | 19 | stage.jobs.push(job) 20 | }) 21 | 22 | stages = stages.map(stage => { 23 | let status 24 | stage.jobs.forEach(job => { 25 | if (job.status === 'failed') { 26 | status = 'failed' 27 | } 28 | if (status === undefined && job.status === 'success') { 29 | status = 'success' 30 | } 31 | if (status === undefined && job.status === 'manual') { 32 | status = 'skipped' 33 | } 34 | }) 35 | 36 | if (status === undefined) { 37 | status = 'pending' 38 | } 39 | 40 | stage.status = status 41 | 42 | return stage 43 | }) 44 | 45 | return stages 46 | } 47 | 48 | /** 49 | * @param {Mozaik} mozaik 50 | */ 51 | module.exports = mozaik => { 52 | mozaik.loadApiConfig(config) 53 | 54 | const gitlab = new Gitlab( 55 | config.get('gitlab.baseUrl'), 56 | config.get('gitlab.token'), 57 | mozaik.request, 58 | mozaik.logger 59 | ) 60 | 61 | return { 62 | project({ project }) { 63 | return gitlab.getProject(project) 64 | }, 65 | projectMembers({ project }) { 66 | return Promise.all([ 67 | gitlab.getProject(project), 68 | gitlab.getProjectMembers(project), 69 | ]).then(([project, members]) => ({ 70 | project, 71 | members, 72 | })) 73 | }, 74 | projectContributors({ project }) { 75 | return Promise.all([ 76 | gitlab.getProject(project), 77 | gitlab.getProjectContributors(project), 78 | ]).then(([project, contributors]) => ({ 79 | project, 80 | contributors, 81 | })) 82 | }, 83 | projectJobs({ project }) { 84 | return Promise.all([gitlab.getProject(project), gitlab.getProjectJobs(project)]).then( 85 | ([project, jobs]) => ({ 86 | project, 87 | jobs, 88 | }) 89 | ) 90 | }, 91 | projectBranches({ project }) { 92 | return Promise.all([ 93 | gitlab.getProject(project), 94 | gitlab.getProjectBranches(project), 95 | ]).then(([project, branches]) => ({ 96 | project, 97 | branches, 98 | })) 99 | }, 100 | projectMergeRequests({ project, query = {} }) { 101 | return gitlab.getProjectMergeRequests(project, query) 102 | }, 103 | projectLabels({ project }) { 104 | return Promise.all([gitlab.getProject(project), gitlab.getProjectLabels(project)]).then( 105 | ([project, labels]) => ({ 106 | project, 107 | labels, 108 | }) 109 | ) 110 | }, 111 | projectMilestones({ project }) { 112 | return Promise.all([ 113 | gitlab.getProject(project), 114 | gitlab.getProjectMilestones(project), 115 | ]).then(([project, milestones]) => ({ 116 | project, 117 | milestones, 118 | })) 119 | }, 120 | projectEvents({ project }) { 121 | return Promise.all([gitlab.getProject(project), gitlab.getProjectEvents(project)]).then( 122 | ([project, events]) => ({ 123 | project, 124 | events, 125 | }) 126 | ) 127 | }, 128 | latestProjectPipeline({ project, ref }) { 129 | return gitlab.getProjectPipelines(project, { ref, per_page: 1 }).then(({ items }) => { 130 | if (items.length === 0) return null 131 | 132 | return Promise.all([ 133 | gitlab.getProject(project), 134 | gitlab.getProjectPipeline(project, items[0].id), 135 | gitlab.getProjectPipelineJobs(project, items[0].id, { per_page: 100 }), 136 | ]).then(([project, pipeline, jobs]) => { 137 | let commit 138 | if (jobs.items.length > 0) { 139 | commit = jobs.items[0].commit 140 | } 141 | 142 | return { 143 | ...pipeline, 144 | project, 145 | commit, 146 | stages: aggregatePipelineJobs(jobs.items), 147 | } 148 | }) 149 | }) 150 | }, 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/components/Project.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | import { 5 | TrapApiError, 6 | Widget, 7 | WidgetBody, 8 | WidgetLoader, 9 | WidgetLabel, 10 | ExternalLink, 11 | LockIcon, 12 | UnlockIcon, 13 | StarIcon, 14 | GitBranchIcon, 15 | typography, 16 | WidgetAvatar, 17 | } from '@mozaik/ui' 18 | 19 | const AVATAR_SIZE = '12vmin' 20 | const ICON_SIZE = '1.8vmin' 21 | 22 | const Container = styled.div` 23 | display: flex; 24 | flex-direction: column; 25 | height: 100%; 26 | ` 27 | 28 | const Header = styled.div` 29 | flex: 1; 30 | display: flex; 31 | align-items: center; 32 | justify-content: center; 33 | ` 34 | 35 | const AvatarPlaceholder = styled.span` 36 | width: ${AVATAR_SIZE}; 37 | height: ${AVATAR_SIZE}; 38 | display: flex; 39 | align-items: center; 40 | justify-content: center; 41 | text-transform: uppercase; 42 | color: ${props => props.theme.colors.textHighlight}; 43 | background: ${props => props.theme.colors.unknown}; 44 | ${props => typography(props.theme, 'display')} font-size: 6vmin; 45 | ` 46 | 47 | const Name = styled.div` 48 | display: flex; 49 | align-items: center; 50 | justify-content: center; 51 | white-space: pre; 52 | color: ${props => props.theme.colors.textHighlight}; 53 | margin: 1vmin 0 3vmin; 54 | ${props => typography(props.theme, 'default', 'strong')}; 55 | ` 56 | 57 | const Grid = styled.div` 58 | display: grid; 59 | grid-template-columns: 1fr 1fr; 60 | grid-column-gap: 2vmin; 61 | grid-row-gap: 2vmin; 62 | ` 63 | 64 | const Count = styled.span` 65 | color: ${props => props.theme.colors.textHighlight}; 66 | ${props => typography(props.theme, 'default', 'strong')}; 67 | ` 68 | 69 | export default class Project extends Component { 70 | static propTypes = { 71 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 72 | apiData: PropTypes.shape({ 73 | name: PropTypes.string.isRequired, 74 | name_with_namespace: PropTypes.string.isRequired, 75 | visibility: PropTypes.string.isRequired, 76 | avatar_url: PropTypes.string, 77 | star_count: PropTypes.number.isRequired, 78 | forks_count: PropTypes.number.isRequired, 79 | }), 80 | apiError: PropTypes.object, 81 | theme: PropTypes.object.isRequired, 82 | } 83 | 84 | static getApiRequest({ project }) { 85 | return { 86 | id: `gitlab.project.${project}`, 87 | params: { project }, 88 | } 89 | } 90 | 91 | render() { 92 | const { apiData: project, apiError, theme } = this.props 93 | 94 | let body = 95 | if (project) { 96 | let visibilityIcon 97 | if (project.visibility === 'public') { 98 | visibilityIcon = ( 99 | 104 | ) 105 | } else { 106 | visibilityIcon = ( 107 | 112 | ) 113 | } 114 | 115 | let avatar 116 | if (project.avatar_url !== null) { 117 | avatar = {project.name_with_namespace} 118 | } else { 119 | avatar = {project.name[0]} 120 | } 121 | 122 | body = ( 123 | 124 |
125 | {avatar} 126 |
127 | 128 | 129 | {project.name_with_namespace} 130 | {' '} 131 | {visibilityIcon} 132 | 133 | 134 | {project.star_count}} 136 | prefix={} 137 | suffix="stars" 138 | /> 139 | {project.forks_count}} 141 | prefix={} 142 | suffix={ 143 | forks 144 | } 145 | /> 146 | 147 |
148 | ) 149 | } 150 | 151 | return ( 152 | 153 | 154 | {body} 155 | 156 | 157 | ) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /test/components/labels/__snapshots__/LabelsBubble.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should render as expected 1`] = ` 4 |
8 |
11 |
15 | 16 | 27 | Labels by open issues 28 |
31 | 3 32 |
33 |
34 |
37 | 48 | 51 | 57 | 58 |
59 |
60 |
64 |
72 |
79 | 84 | 87 | 102 | 117 | 132 | 149 | label A 21 150 | 151 | 168 | label B 15 169 | 170 | 187 | label C 11 188 | 189 | 190 | 191 |
192 |
193 |
194 |
195 |
196 | `; 197 | -------------------------------------------------------------------------------- /src/client/gitlab.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chalk = require('chalk') 4 | 5 | /** 6 | * @typedef {Object} Pagination 7 | * @property {number} total 8 | * @property {number} page 9 | * @property {number} pages 10 | * @property {number} perPage 11 | * @property {number} previousPage 12 | * @property {number} nextPage 13 | */ 14 | 15 | /** 16 | * @typedef {Object} PaginationOptions 17 | * @property {number} [per_page] 18 | * @property {number} [page] 19 | */ 20 | 21 | /** 22 | * @typedef {Object} PipelineOptions 23 | * @property {number} [per_page] 24 | * @property {number} [page] 25 | * @property {'running'|'pending'|'finished'|'branches'|'tags'} [scope] - The scope of pipelines, one of: running, pending, finished, branches, tags 26 | * @property {'running'|'pending'|'success'|'failed'|'canceled'|'skipped'} [status] - The status of pipelines, one of: running, pending, success, failed, canceled, skipped 27 | * @property {string} [ref] - The ref of pipelines 28 | * @property {string} [sha] - The sha or pipelines 29 | * @property {boolean} [yaml_errors] - Returns pipelines with invalid configurations 30 | * @property {string} [name] - The name of the user who triggered pipelines 31 | * @property {string} [username] - The username of the user who triggered pipelines 32 | * @property {'id'|'status'|'ref'|'user_id'} [order_by] - Order pipelines by id, status, ref, or user_id (default: id) 33 | * @property {'asc'|'desc'} [sort] - Sort pipelines in asc or desc order (default: desc) 34 | */ 35 | 36 | /** 37 | * @param {object} headers 38 | * @return {Pagination} 39 | */ 40 | exports.paginationFromHeaders = headers => ({ 41 | total: Number(headers['x-total']), 42 | page: Number(headers['x-page']), 43 | pages: Number(headers['x-total-pages']), 44 | perPage: Number(headers['x-per-page']), 45 | previousPage: Number(headers['x-prev-page']), 46 | nextPage: Number(headers['x-next-page']), 47 | }) 48 | 49 | class GitLab { 50 | constructor(baseUrl, token, request, logger) { 51 | this.baseUrl = baseUrl 52 | this.token = token 53 | this.request = request 54 | this.logger = logger 55 | } 56 | 57 | makeRequest(path, qs) { 58 | const uri = `${this.baseUrl}${path}` 59 | 60 | const options = { 61 | uri, 62 | qs, 63 | json: true, 64 | resolveWithFullResponse: true, 65 | headers: { 66 | 'PRIVATE-TOKEN': this.token, 67 | }, 68 | } 69 | 70 | const paramsDebug = qs ? ` ${JSON.stringify(qs)}` : '' 71 | this.logger.info(chalk.yellow(`[gitlab] calling ${uri}${paramsDebug}`)) 72 | 73 | return this.request(options) 74 | } 75 | 76 | getProject(projectId) { 77 | return this.makeRequest(`/projects/${encodeURIComponent(projectId)}`).then(res => res.body) 78 | } 79 | 80 | getProjectMembers(projectId) { 81 | return this.makeRequest(`/projects/${encodeURIComponent(projectId)}/members`).then(res => ({ 82 | items: res.body, 83 | pagination: exports.paginationFromHeaders(res.headers), 84 | })) 85 | } 86 | 87 | getProjectContributors(projectId) { 88 | return this.makeRequest( 89 | `/projects/${encodeURIComponent(projectId)}/repository/contributors` 90 | ).then(res => ({ 91 | items: res.body, 92 | pagination: exports.paginationFromHeaders(res.headers), 93 | })) 94 | } 95 | 96 | getProjectJobs(projectId) { 97 | return this.makeRequest(`/projects/${encodeURIComponent(projectId)}/jobs`, { 98 | per_page: 40, 99 | }).then(res => ({ 100 | items: res.body, 101 | pagination: exports.paginationFromHeaders(res.headers), 102 | })) 103 | } 104 | 105 | getProjectBranches(projectId) { 106 | return this.makeRequest( 107 | `/projects/${encodeURIComponent(projectId)}/repository/branches` 108 | ).then(res => ({ 109 | items: res.body, 110 | pagination: exports.paginationFromHeaders(res.headers), 111 | })) 112 | } 113 | 114 | getProjectMergeRequests(projectId, query = {}) { 115 | return this.makeRequest( 116 | `/projects/${encodeURIComponent(projectId)}/merge_requests`, 117 | query 118 | ).then(res => ({ 119 | items: res.body, 120 | pagination: exports.paginationFromHeaders(res.headers), 121 | })) 122 | } 123 | 124 | getProjectLabels(projectId) { 125 | return this.makeRequest(`/projects/${encodeURIComponent(projectId)}/labels`).then(res => ({ 126 | items: res.body, 127 | pagination: exports.paginationFromHeaders(res.headers), 128 | })) 129 | } 130 | 131 | getProjectMilestones(projectId) { 132 | return this.makeRequest(`/projects/${encodeURIComponent(projectId)}/milestones`).then( 133 | res => ({ 134 | items: res.body, 135 | pagination: exports.paginationFromHeaders(res.headers), 136 | }) 137 | ) 138 | } 139 | 140 | getProjectEvents(projectId) { 141 | return this.makeRequest(`/projects/${encodeURIComponent(projectId)}/events`).then(res => ({ 142 | items: res.body, 143 | pagination: exports.paginationFromHeaders(res.headers), 144 | })) 145 | } 146 | 147 | /** 148 | * @param {string|number} projectId 149 | * @param {PipelineOptions} options 150 | * 151 | * @return {Promise} 152 | */ 153 | getProjectPipelines(projectId, options = {}) { 154 | return this.makeRequest( 155 | `/projects/${encodeURIComponent(projectId)}/pipelines`, 156 | options 157 | ).then(res => ({ 158 | items: res.body, 159 | pagination: exports.paginationFromHeaders(res.headers), 160 | })) 161 | } 162 | 163 | /** 164 | * @param {string|number} projectId 165 | * @param {number} pipelineId 166 | * 167 | * @return {Promise} 168 | */ 169 | getProjectPipeline(projectId, pipelineId) { 170 | return this.makeRequest( 171 | `/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}` 172 | ).then(res => res.body) 173 | } 174 | 175 | /** 176 | * @param {string|number} projectId 177 | * @param {number} pipelineId 178 | * @param {PaginationOptions} options 179 | * 180 | * @return {Promise} 181 | */ 182 | getProjectPipelineJobs(projectId, pipelineId, options = {}) { 183 | return this.makeRequest( 184 | `/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/jobs`, 185 | options 186 | ).then(res => ({ 187 | items: res.body, 188 | pagination: exports.paginationFromHeaders(res.headers), 189 | })) 190 | } 191 | } 192 | 193 | exports.Gitlab = GitLab 194 | -------------------------------------------------------------------------------- /fixtures/milestones.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 339359, 4 | "iid": 34, 5 | "project_id": 250833, 6 | "title": "10.0", 7 | "description": "", 8 | "state": "closed", 9 | "created_at": "2017-07-03T13:55:49.930Z", 10 | "updated_at": "2018-02-26T11:26:06.732Z", 11 | "due_date": "2017-09-22", 12 | "start_date": "2017-08-08", 13 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/34" 14 | }, 15 | { 16 | "id": 334042, 17 | "iid": 31, 18 | "project_id": 250833, 19 | "title": "9.5", 20 | "description": "", 21 | "state": "closed", 22 | "created_at": "2017-06-22T11:52:49.133Z", 23 | "updated_at": "2018-06-25T17:30:56.425Z", 24 | "due_date": null, 25 | "start_date": null, 26 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/31" 27 | }, 28 | { 29 | "id": 305236, 30 | "iid": 29, 31 | "project_id": 250833, 32 | "title": "9.4", 33 | "description": "", 34 | "state": "closed", 35 | "created_at": "2017-04-26T13:01:26.891Z", 36 | "updated_at": "2018-01-16T12:44:57.772Z", 37 | "due_date": "2017-07-22", 38 | "start_date": "2017-06-07", 39 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/29" 40 | }, 41 | { 42 | "id": 297006, 43 | "iid": 28, 44 | "project_id": 250833, 45 | "title": "v9.2", 46 | "description": "", 47 | "state": "closed", 48 | "created_at": "2017-04-10T10:58:54.693Z", 49 | "updated_at": "2017-12-18T08:38:35.143Z", 50 | "due_date": "2017-05-22", 51 | "start_date": "2017-04-23", 52 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/28" 53 | }, 54 | { 55 | "id": 286735, 56 | "iid": 27, 57 | "project_id": 250833, 58 | "title": "9.3", 59 | "description": "", 60 | "state": "closed", 61 | "created_at": "2017-03-21T10:10:18.543Z", 62 | "updated_at": "2018-01-16T11:41:27.393Z", 63 | "due_date": "2017-06-22", 64 | "start_date": "2017-05-23", 65 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/27" 66 | }, 67 | { 68 | "id": 271327, 69 | "iid": 26, 70 | "project_id": 250833, 71 | "title": "v9.1", 72 | "description": "", 73 | "state": "closed", 74 | "created_at": "2017-02-22T15:50:06.892Z", 75 | "updated_at": "2017-07-27T16:39:17.782Z", 76 | "due_date": "2017-04-22", 77 | "start_date": "2017-03-23", 78 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/26" 79 | }, 80 | { 81 | "id": 154453, 82 | "iid": 25, 83 | "project_id": 250833, 84 | "title": "v1.11", 85 | "description": "", 86 | "state": "closed", 87 | "created_at": "2017-01-20T03:49:39.119Z", 88 | "updated_at": "2017-07-27T16:39:12.096Z", 89 | "due_date": "2017-02-22", 90 | "start_date": null, 91 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/25" 92 | }, 93 | { 94 | "id": 150961, 95 | "iid": 24, 96 | "project_id": 250833, 97 | "title": "v9.0", 98 | "description": "", 99 | "state": "closed", 100 | "created_at": "2017-01-12T04:14:02.925Z", 101 | "updated_at": "2017-05-25T11:16:35.641Z", 102 | "due_date": "2017-03-22", 103 | "start_date": null, 104 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/24" 105 | }, 106 | { 107 | "id": 142706, 108 | "iid": 23, 109 | "project_id": 250833, 110 | "title": "v1.10", 111 | "description": "", 112 | "state": "closed", 113 | "created_at": "2016-12-20T15:08:09.237Z", 114 | "updated_at": "2017-04-24T11:16:03.339Z", 115 | "due_date": "2017-01-22", 116 | "start_date": null, 117 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/23" 118 | }, 119 | { 120 | "id": 128186, 121 | "iid": 22, 122 | "project_id": 250833, 123 | "title": "v1.9", 124 | "description": "", 125 | "state": "closed", 126 | "created_at": "2016-11-15T16:40:24.951Z", 127 | "updated_at": "2017-03-22T16:50:33.032Z", 128 | "due_date": "2016-12-22", 129 | "start_date": null, 130 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/22" 131 | }, 132 | { 133 | "id": 116215, 134 | "iid": 21, 135 | "project_id": 250833, 136 | "title": "v1.8", 137 | "description": "", 138 | "state": "closed", 139 | "created_at": "2016-10-18T17:18:03.958Z", 140 | "updated_at": "2017-02-22T15:49:47.159Z", 141 | "due_date": "2016-11-22", 142 | "start_date": null, 143 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/21" 144 | }, 145 | { 146 | "id": 98345, 147 | "iid": 19, 148 | "project_id": 250833, 149 | "title": "v1.7", 150 | "description": "", 151 | "state": "closed", 152 | "created_at": "2016-09-08T11:23:09.357Z", 153 | "updated_at": "2017-01-23T15:47:46.205Z", 154 | "due_date": "2016-10-22", 155 | "start_date": null, 156 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/19" 157 | }, 158 | { 159 | "id": 83430, 160 | "iid": 18, 161 | "project_id": 250833, 162 | "title": "v1.6", 163 | "description": "", 164 | "state": "closed", 165 | "created_at": "2016-08-02T12:28:28.068Z", 166 | "updated_at": "2016-12-22T11:09:36.216Z", 167 | "due_date": "2016-09-22", 168 | "start_date": null, 169 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/18" 170 | }, 171 | { 172 | "id": 75603, 173 | "iid": 17, 174 | "project_id": 250833, 175 | "title": "v1.5", 176 | "description": "", 177 | "state": "closed", 178 | "created_at": "2016-06-30T15:17:57.030Z", 179 | "updated_at": "2016-11-22T17:13:00.787Z", 180 | "due_date": "2016-08-22", 181 | "start_date": null, 182 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/17" 183 | }, 184 | { 185 | "id": 52992, 186 | "iid": 13, 187 | "project_id": 250833, 188 | "title": "v1.4", 189 | "description": "", 190 | "state": "closed", 191 | "created_at": "2016-03-29T13:54:55.647Z", 192 | "updated_at": "2016-10-21T20:22:49.060Z", 193 | "due_date": "2016-07-22", 194 | "start_date": null, 195 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/13" 196 | }, 197 | { 198 | "id": 52991, 199 | "iid": 12, 200 | "project_id": 250833, 201 | "title": "v1.3", 202 | "description": "", 203 | "state": "closed", 204 | "created_at": "2016-03-29T13:54:44.346Z", 205 | "updated_at": "2016-09-22T18:16:22.791Z", 206 | "due_date": "2016-06-22", 207 | "start_date": null, 208 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/12" 209 | }, 210 | { 211 | "id": 47833, 212 | "iid": 11, 213 | "project_id": 250833, 214 | "title": "8.9", 215 | "description": "", 216 | "state": "closed", 217 | "created_at": "2016-03-04T13:38:40.580Z", 218 | "updated_at": "2016-03-29T13:55:20.953Z", 219 | "due_date": "2016-06-22", 220 | "start_date": null, 221 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/11" 222 | }, 223 | { 224 | "id": 44921, 225 | "iid": 10, 226 | "project_id": 250833, 227 | "title": "v1.2", 228 | "description": "", 229 | "state": "closed", 230 | "created_at": "2016-02-18T12:03:23.894Z", 231 | "updated_at": "2016-08-08T10:03:18.495Z", 232 | "due_date": "2016-05-09", 233 | "start_date": null, 234 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/10" 235 | }, 236 | { 237 | "id": 42371, 238 | "iid": 9, 239 | "project_id": 250833, 240 | "title": "8.8", 241 | "description": "", 242 | "state": "closed", 243 | "created_at": "2016-02-03T12:47:20.411Z", 244 | "updated_at": "2016-03-29T13:55:22.832Z", 245 | "due_date": "2016-05-22", 246 | "start_date": null, 247 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/9" 248 | }, 249 | { 250 | "id": 35371, 251 | "iid": 8, 252 | "project_id": 250833, 253 | "title": "v1.1", 254 | "description": "", 255 | "state": "closed", 256 | "created_at": "2015-12-14T15:03:49.680Z", 257 | "updated_at": "2016-08-08T10:03:13.906Z", 258 | "due_date": "2016-03-07", 259 | "start_date": null, 260 | "web_url": "https://gitlab.com/gitlab-org/gitlab-runner/milestones/8" 261 | } 262 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mozaïk GitLab widgets 2 | 3 | [![License][license-image]][license-url] 4 | [![Travis CI][travis-image]][travis-url] 5 | [![NPM version][npm-image]][npm-url] 6 | [![Coverage Status][coverage-image]][coverage-url] 7 | ![widget count][widget-count-image] 8 | 9 | This repository contains some widgets to use with [Mozaïk](https://github.com/plouc/mozaik). 10 | 11 | ![Sample gitlab dashboard](preview/sample_dashboard.png) 12 | 13 | You can see a live demo of this extension [here](http://mozaik-gitlab.herokuapp.com/) 14 | 15 | [![Deploy][heroku-image]][heroku-url] 16 | 17 | > This branch contains code for the version compatible with 18 | > Mozaïk v2, if you're looking for v1, please use 19 | > [mozaik-1 branch](https://github.com/plouc/mozaik-ext-gitlab/tree/mozaik-1). 20 | 21 | - [client configuration](#client-configuration) 22 | - widgets 23 | - [Project](#project) 24 | - [Project Members](#project-members) 25 | - [Project Contributors](#gitlab-project-contributors) 26 | - [Branches](#gitlab-branches) 27 | - Pipelines 28 | - [LatestProjectPipeline](#latestprojectpipeline) 29 | - Jobs 30 | - [Job Histogram](#gitlab-job-histogram) 31 | - [Job History](#gitlab-job-history) 32 | - Labels 33 | - [Labels Bubble chart](#gitlab-labels-bubble-chart) 34 | - [Labels Pie](#gitlab-labels-pie) 35 | - [Labels Tree map](#gitlab-labels-tree-map) 36 | 37 | ## Client Configuration 38 | 39 | In order to use the Mozaïk gitlab extension, you must configure its **client**. 40 | Configuration is loaded from environment variables. 41 | 42 | | env key | required | default | description 43 | |------------------|----------|---------|---------------------------- 44 | | GITLAB_BASE_URL | yes | | gitlab API base url, eg. `'https://gitlab.com/api/v4` 45 | | GITLAB_API_TOKEN | yes | | gitlab API token 46 | 47 | ## Project 48 | 49 | > Show GitLab project info. 50 | 51 | ![Gitlab project](preview/project.png) 52 | 53 | ### parameters 54 | 55 | key | required | description 56 | ----------|----------|-------------------------- 57 | `project` | yes | *ID or NAMESPACE/PROJECT_NAME of a project* 58 | 59 | ### usage 60 | 61 | ``` yaml 62 | - extension: gitlab 63 | widget: Project 64 | project: gitlab-org/gitlab-ce 65 | columns: 1 66 | rows: 1 67 | x: 0 68 | y: 0 69 | ``` 70 | 71 | ## Project Members 72 | 73 | > Show GitLab project members. 74 | 75 | ![Gitlab project members](preview/project_members.png) 76 | 77 | ### parameters 78 | 79 | key | required | description 80 | ----------|----------|-------------------------- 81 | `project` | yes | *ID or NAMESPACE/PROJECT_NAME of a project* 82 | 83 | ### usage 84 | 85 | ``` yaml 86 | - extension: gitlab 87 | widget: ProjectMembers 88 | project: gitlab-org/gitlab-ce 89 | columns: 1 90 | rows: 1 91 | x: 0 92 | y: 0 93 | ``` 94 | 95 | ## GitLab Branches 96 | 97 | > Show GitLab project branches. 98 | 99 | ![Gitlab project branches](https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/master/preview/gitlab_branches.png) 100 | 101 | ### parameters 102 | 103 | key | required | description 104 | ----------|----------|-------------------------- 105 | `project` | yes | *ID or NAMESPACE/PROJECT_NAME of a project* 106 | 107 | ### usage 108 | 109 | ``` yaml 110 | - extension: gitlab 111 | widget: Branches 112 | project: gitlab-org/gitlab-ce 113 | columns: 1 114 | rows: 1 115 | x: 0 116 | y: 0 117 | ``` 118 | 119 | ## LatestProjectPipeline 120 | 121 | > Show details about latest project pipeline. 122 | 123 | ![LatestProjectPipeline](preview/latest_project_pipeline.png) 124 | 125 | ### parameters 126 | 127 | key | type | required | default | description 128 | --------------------|-------------|----------|---------|------------- 129 | `project` | `{string}` | yes | | ID or NAMESPACE/PROJECT_NAME of a project 130 | `gitRef` | `{string}` | no | | Get latest pipeline for a specific ref (branch, tag…) 131 | `hideCommitMessage` | `{boolean}` | no | `false` | Hide pipeline commit info 132 | 133 | ### usage 134 | 135 | ``` yaml 136 | - extension: gitlab 137 | widget: LatestProjectPipeline 138 | project: gitlab-org/gitlab-runner 139 | gitRef: master 140 | hideCommitMessage: false 141 | columns: 2 142 | rows: 1 143 | x: 0 144 | y: 0 145 | ``` 146 | 147 | ## GitLab Job Histogram 148 | 149 | > Show GitLab project job histogram. 150 | 151 | ![Gitlab project job histogram](https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/master/preview/gitlab_build_histogram.png) 152 | 153 | ### parameters 154 | 155 | key | required | description 156 | ----------|----------|-------------------------- 157 | `project` | yes | *ID or NAMESPACE/PROJECT_NAME of a project* 158 | 159 | ### usage 160 | 161 | ``` yaml 162 | - extension: gitlab 163 | widget: JobHistogram 164 | project: gitlab-org/gitlab-ce 165 | columns: 2 166 | rows: 1 167 | x: 0 168 | y: 0 169 | ``` 170 | 171 | ## GitLab Job History 172 | 173 | > Show GitLab project job history. 174 | 175 | ![Gitlab project job history](https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/master/preview/gitlab_build_history.png) 176 | 177 | ### parameters 178 | 179 | key | required | description 180 | ----------|----------|-------------------------- 181 | `project` | yes | *ID or NAMESPACE/PROJECT_NAME of a project* 182 | 183 | ### usage 184 | 185 | ``` yaml 186 | - extension: gitlab 187 | widget: JobHistory 188 | project: gitlab-org/gitlab-ce 189 | columns: 1 190 | rows: 1 191 | x: 0 192 | y: 0 193 | ``` 194 | 195 | ## GitLab Project Contributors 196 | 197 | > Show GitLab project contributors. 198 | 199 | ![Gitlab project contributors](https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/master/preview/gitlab_project_contributors.png) 200 | 201 | ### parameters 202 | 203 | key | required | description 204 | ----------|----------|-------------------------- 205 | `project` | yes | *ID or NAMESPACE/PROJECT_NAME of a project* 206 | 207 | ### usage 208 | 209 | ``` yaml 210 | - extension: gitlab 211 | widget: ProjectContributors 212 | project: gitlab-org/gitlab-ce 213 | columns: 1 214 | rows: 1 215 | x: 0 216 | y: 0 217 | ``` 218 | 219 | ## GitLab labels bubble chart 220 | 221 | > Show GitLab project's labels stats using a bubble chart. 222 | 223 | ![Gitlab labels bubble chart](https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/master/preview/gitlab_labels_bubble.png) 224 | 225 | ### parameters 226 | 227 | key | required | default | description 228 | ----------|----------|-----------------------|---------------- 229 | `project` | yes | *n/a* | *ID or NAMESPACE/PROJECT_NAME of a project* 230 | `countBy` | yes | `'open_issues_count'` | *Defines which count to use, must be one of: `'open_issues_count'`, `'closed_issues_count'`, `'open_merge_requests_count'`* 231 | `title` | no | *n/a* | *Overrides widget title* 232 | 233 | ### usage 234 | 235 | ``` yaml 236 | - extension: gitlab 237 | widget: LabelsBubble 238 | project: gitlab-org/gitlab-ce 239 | columns: 1 240 | rows: 1 241 | x: 0 242 | y: 0 243 | ``` 244 | 245 | ## GitLab labels pie 246 | 247 | > Show GitLab project's labels stats using a pie chart. 248 | 249 | ![Gitlab labels pie](https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/master/preview/gitlab_labels_pie.png) 250 | 251 | ### parameters 252 | 253 | key | required | default | description 254 | ----------|----------|-----------------------|---------------- 255 | `project` | yes | *n/a* | *ID or NAMESPACE/PROJECT_NAME of a project* 256 | `countBy` | yes | `'open_issues_count'` | *Defines which count to use, must be one of: `'open_issues_count'`, `'closed_issues_count'`, `'open_merge_requests_count'`* 257 | `title` | no | *n/a* | *Overrides widget title* 258 | 259 | ### usage 260 | 261 | ``` yaml 262 | - extension: gitlab 263 | widget: LabelsPie 264 | project: gitlab-org/gitlab-ce 265 | columns: 1 266 | rows: 1 267 | x: 0 268 | y: 0 269 | ``` 270 | 271 | ## GitLab labels tree map 272 | 273 | > Show GitLab project's labels stats using a tree map chart. 274 | 275 | ![Gitlab labels tree map](https://raw.githubusercontent.com/plouc/mozaik-ext-gitlab/master/preview/gitlab_labels_treemap.png) 276 | 277 | ### parameters 278 | 279 | key | required | default | description 280 | ----------|----------|-----------------------|---------------- 281 | `project` | yes | *n/a* | *ID or NAMESPACE/PROJECT_NAME of a project* 282 | `countBy` | yes | `'open_issues_count'` | *Defines which count to use, must be one of: `'open_issues_count'`, `'closed_issues_count'`, `'open_merge_requests_count'`* 283 | `title` | no | *n/a* | *Overrides widget title* 284 | 285 | ### usage 286 | 287 | ``` yaml 288 | - extension: gitlab 289 | widget: LabelsTreemap 290 | project: gitlab-org/gitlab-ce 291 | columns: 1 292 | rows: 1 293 | x: 0 294 | y: 0 295 | ``` 296 | 297 | 298 | [license-image]: https://img.shields.io/github/license/plouc/mozaik-ext-gitlab.svg?style=flat-square 299 | [license-url]: https://github.com/plouc/mozaik-ext-gitlab/blob/master/LICENSE.md 300 | [travis-image]: https://img.shields.io/travis/plouc/mozaik-ext-gitlab.svg?style=flat-square 301 | [travis-url]: https://travis-ci.org/plouc/mozaik-ext-gitlab 302 | [npm-image]: https://img.shields.io/npm/v/@mozaik/ext-gitlab.svg?style=flat-square 303 | [npm-url]: https://www.npmjs.com/package/@mozaik/ext-gitlab 304 | [coverage-image]: https://img.shields.io/coveralls/plouc/mozaik-ext-gitlab.svg?style=flat-square 305 | [coverage-url]: https://coveralls.io/github/plouc/mozaik-ext-gitlab 306 | [widget-count-image]: https://img.shields.io/badge/widgets-x12-green.svg?style=flat-square 307 | [heroku-image]: https://www.herokucdn.com/deploy/button.svg 308 | [heroku-url]: https://heroku.com/deploy?template=https://github.com/plouc/mozaik-ext-gitlab/tree/demo 309 | -------------------------------------------------------------------------------- /src/components/pipelines/LatestProjectPipeline.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | import { truncate } from 'lodash' 5 | import moment from 'moment' 6 | import { 7 | TrapApiError, 8 | Widget, 9 | WidgetBody, 10 | WidgetLoader, 11 | ClockIcon, 12 | GitBranchIcon, 13 | GitCommitIcon, 14 | PauseCircleIcon, 15 | CheckCircleIcon, 16 | AlertCircleIcon, 17 | FastForwardIcon, 18 | ExternalLink, 19 | WidgetAvatar, 20 | typography, 21 | } from '@mozaik/ui' 22 | 23 | const Container = styled.div` 24 | display: grid; 25 | grid-template-columns: 3vmin auto; 26 | grid-column-gap: 2vmin; 27 | height: 100%; 28 | padding-right: 2vmin; 29 | ` 30 | 31 | const InnerContainer = styled.div` 32 | padding-top: 1.4vmin; 33 | display: flex; 34 | height: 100%; 35 | flex-direction: column; 36 | ` 37 | 38 | const Header = styled.div` 39 | display: flex; 40 | align-items: center; 41 | justify-content: space-between; 42 | ` 43 | 44 | const Content = styled.div` 45 | flex: 1; 46 | display: flex; 47 | align-items: center; 48 | ${props => typography(props.theme, 'default', 'small')}; 49 | ` 50 | 51 | const Footer = styled.div` 52 | display: flex; 53 | align-items: flex-end; 54 | justify-content: flex-start; 55 | ` 56 | 57 | const Status = styled.div` 58 | display: flex; 59 | justify-content: center; 60 | padding-top: 1.7vmin; 61 | ` 62 | 63 | const InfoItem = styled.div` 64 | white-space: pre; 65 | ${props => typography(props.theme, 'default', 'small')}; 66 | ` 67 | 68 | const Stages = styled.div` 69 | display: grid; 70 | grid-column-gap: 2px; 71 | ` 72 | 73 | const Stage = styled.div` 74 | align-self: end; 75 | max-width: 36vmin; 76 | overflow: hidden; 77 | ` 78 | 79 | const StageLabel = styled.div` 80 | padding: 0 2vmin 0 1vmin; 81 | white-space: pre; 82 | ${props => typography(props.theme, 'default', 'small')}; 83 | ` 84 | 85 | const StageIndicator = styled.div` 86 | height: 0.6vmin; 87 | margin-top: 1vmin; 88 | ` 89 | 90 | export const colorByStatus = (colors, status) => { 91 | if (status === 'skipped' || status === 'canceled') { 92 | return colors.unknown 93 | } 94 | 95 | if (status === 'running' || status === 'pending' || status === 'success_with_error') { 96 | return colors.warning 97 | } 98 | 99 | if (status === 'success') { 100 | return colors.success 101 | } 102 | 103 | return colors.failure 104 | } 105 | 106 | export const iconByStatus = status => { 107 | if (status === 'success') { 108 | return CheckCircleIcon 109 | } 110 | 111 | if (status === 'failed') { 112 | return AlertCircleIcon 113 | } 114 | 115 | if (status === 'skipped') { 116 | return FastForwardIcon 117 | } 118 | 119 | return PauseCircleIcon 120 | } 121 | 122 | export default class LatestProjectPipeline extends Component { 123 | static propTypes = { 124 | project: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 125 | gitRef: PropTypes.string, 126 | hideCommitMessage: PropTypes.bool.isRequired, 127 | apiData: PropTypes.shape({ 128 | id: PropTypes.number.isRequired, 129 | ref: PropTypes.string.isRequired, 130 | started_at: PropTypes.string.isRequired, 131 | duration: PropTypes.number, 132 | status: PropTypes.oneOf([ 133 | 'running', 134 | 'pending', 135 | 'success', 136 | 'failed', 137 | 'canceled', 138 | 'skipped', 139 | ]).isRequired, 140 | project: PropTypes.shape({ 141 | web_url: PropTypes.string.isRequired, 142 | }).isRequired, 143 | user: PropTypes.shape({ 144 | name: PropTypes.string.isRequired, 145 | web_url: PropTypes.string.isRequired, 146 | avatar_url: PropTypes.string.isRequired, 147 | }).isRequired, 148 | commit: PropTypes.shape({ 149 | id: PropTypes.string.isRequired, 150 | short_id: PropTypes.string.isRequired, 151 | }).isRequired, 152 | stages: PropTypes.arrayOf( 153 | PropTypes.shape({ 154 | name: PropTypes.string.isRequired, 155 | status: PropTypes.oneOf([ 156 | 'running', 157 | 'pending', 158 | 'success', 159 | 'failed', 160 | 'success_with_error', 161 | 'canceled', 162 | 'skipped', 163 | 'manual', 164 | ]).isRequired, 165 | }) 166 | ).isRequired, 167 | }), 168 | apiError: PropTypes.object, 169 | theme: PropTypes.object.isRequired, 170 | } 171 | 172 | static defaultProps = { 173 | hideCommitMessage: false, 174 | } 175 | 176 | static getApiRequest({ project, gitRef }) { 177 | let id = `gitlab.latestProjectPipeline.${project}` 178 | if (gitRef !== undefined) { 179 | id += `.${gitRef}` 180 | } 181 | 182 | return { 183 | id, 184 | params: { project, ref: gitRef }, 185 | } 186 | } 187 | 188 | render() { 189 | const { hideCommitMessage, apiData, apiError, theme } = this.props 190 | 191 | let content = 192 | if (apiData && !apiError) { 193 | const GlobalIcon = iconByStatus(apiData.status) 194 | 195 | content = ( 196 | 197 | 203 | 204 | 205 | 206 |
207 | 208 | 211 | #{apiData.id} 212 | {' '} 213 | by{' '} 214 | 218 | {apiData.user.name} 219 | {' '} 220 | 221 | {apiData.user.name} 222 | 223 | 224 | 225 | {' '} 232 | 235 | {apiData.ref} 236 | {' '} 237 | {' '} 244 | 247 | {apiData.commit.short_id} 248 | 249 | 250 | 251 | {' '} 258 | {moment(apiData.started_at).fromNow()} 259 | 260 |
261 | 262 | {hideCommitMessage 263 | ? '' 264 | : truncate(apiData.commit.message, { length: 80 })} 265 | 266 |
267 | '1fr').join(' '), 270 | }} 271 | > 272 | {apiData.stages.map(stage => { 273 | const StageIcon = iconByStatus(stage.status) 274 | 275 | return ( 276 | 277 | 278 | {' '} 289 | {stage.name} 290 | 291 | 299 | 300 | ) 301 | })} 302 | 303 |
304 |
305 |
306 | ) 307 | } 308 | 309 | return ( 310 | 311 | 312 | {content} 313 | 314 | 315 | ) 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /fixtures/branches.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "0-6-stable", 4 | "commit": { 5 | "id": "3227f0aa5be1d64d2ec694bd3758e0d43e92b36b", 6 | "short_id": "3227f0aa", 7 | "title": "Fix 0.6.2 release", 8 | "created_at": "2015-10-22T09:38:08.000+00:00", 9 | "parent_ids": null, 10 | "message": "Fix 0.6.2 release", 11 | "author_name": "Kamil Trzcinski", 12 | "author_email": "ayufan@ayufan.eu", 13 | "authored_date": "2015-10-22T08:09:43.000+00:00", 14 | "committer_name": "Kamil Trzcinski", 15 | "committer_email": "ayufan@ayufan.eu", 16 | "committed_date": "2015-10-22T09:38:08.000+00:00" 17 | }, 18 | "merged": false, 19 | "protected": true, 20 | "developers_can_push": false, 21 | "developers_can_merge": false, 22 | "can_push": false 23 | }, 24 | { 25 | "name": "0-7-stable", 26 | "commit": { 27 | "id": "998cf5d5ef3caf6535cc4c5f3279b08c3ee2ecc8", 28 | "short_id": "998cf5d5", 29 | "title": "Fix compilation error", 30 | "created_at": "2015-11-25T10:00:41.000+00:00", 31 | "parent_ids": null, 32 | "message": "Fix compilation error", 33 | "author_name": "Kamil Trzcinski", 34 | "author_email": "ayufan@ayufan.eu", 35 | "authored_date": "2015-11-25T10:00:41.000+00:00", 36 | "committer_name": "Kamil Trzcinski", 37 | "committer_email": "ayufan@ayufan.eu", 38 | "committed_date": "2015-11-25T10:00:41.000+00:00" 39 | }, 40 | "merged": true, 41 | "protected": true, 42 | "developers_can_push": false, 43 | "developers_can_merge": false, 44 | "can_push": false 45 | }, 46 | { 47 | "name": "1-0-stable", 48 | "commit": { 49 | "id": "907cfa5e0682e938691ba5f5d665ff8147833a89", 50 | "short_id": "907cfa5e", 51 | "title": "Fix error level checking for Windows Batch and PowerShell", 52 | "created_at": "2016-03-23T12:42:14.000+00:00", 53 | "parent_ids": null, 54 | "message": "Fix error level checking for Windows Batch and PowerShell", 55 | "author_name": "Kamil Trzcinski", 56 | "author_email": "ayufan@ayufan.eu", 57 | "authored_date": "2016-03-23T12:42:14.000+00:00", 58 | "committer_name": "Kamil Trzcinski", 59 | "committer_email": "ayufan@ayufan.eu", 60 | "committed_date": "2016-03-23T12:42:14.000+00:00" 61 | }, 62 | "merged": false, 63 | "protected": true, 64 | "developers_can_push": false, 65 | "developers_can_merge": false, 66 | "can_push": false 67 | }, 68 | { 69 | "name": "1-1-stable", 70 | "commit": { 71 | "id": "67d3a962c2fab497b2ea5aea5730df49ae7ce5eb", 72 | "short_id": "67d3a962", 73 | "title": "Add CI_BUILD_TOKEN", 74 | "created_at": "2016-05-22T16:14:02.000+00:00", 75 | "parent_ids": null, 76 | "message": "Add CI_BUILD_TOKEN", 77 | "author_name": "Kamil Trzcinski", 78 | "author_email": "ayufan@ayufan.eu", 79 | "authored_date": "2016-04-14T21:16:05.000+00:00", 80 | "committer_name": "Kamil Trzcinski", 81 | "committer_email": "ayufan@ayufan.eu", 82 | "committed_date": "2016-05-22T16:14:02.000+00:00" 83 | }, 84 | "merged": false, 85 | "protected": true, 86 | "developers_can_push": false, 87 | "developers_can_merge": false, 88 | "can_push": false 89 | }, 90 | { 91 | "name": "1-10-stable", 92 | "commit": { 93 | "id": "2c34bd0f2b7095836d943c3764bd78b7dee91296", 94 | "short_id": "2c34bd0f", 95 | "title": "Update CHANGELOG for v1.10.8", 96 | "created_at": "2017-04-04T18:16:37.000+00:00", 97 | "parent_ids": null, 98 | "message": "Update CHANGELOG for v1.10.8", 99 | "author_name": "Tomasz Maczukin", 100 | "author_email": "tomasz@maczukin.pl", 101 | "authored_date": "2017-04-04T18:16:37.000+00:00", 102 | "committer_name": "Tomasz Maczukin", 103 | "committer_email": "tomasz@maczukin.pl", 104 | "committed_date": "2017-04-04T18:16:37.000+00:00" 105 | }, 106 | "merged": false, 107 | "protected": true, 108 | "developers_can_push": false, 109 | "developers_can_merge": false, 110 | "can_push": false 111 | }, 112 | { 113 | "name": "1-11-stable", 114 | "commit": { 115 | "id": "3f23e33b8146487592f303ab04cc68cdfb6364e9", 116 | "short_id": "3f23e33b", 117 | "title": "Bump version to 1.11.6", 118 | "created_at": "2017-07-04T13:38:02.000+00:00", 119 | "parent_ids": null, 120 | "message": "Bump version to 1.11.6", 121 | "author_name": "Tomasz Maczukin", 122 | "author_email": "tomasz@maczukin.pl", 123 | "authored_date": "2017-07-04T13:38:02.000+00:00", 124 | "committer_name": "Tomasz Maczukin", 125 | "committer_email": "tomasz@maczukin.pl", 126 | "committed_date": "2017-07-04T13:38:02.000+00:00" 127 | }, 128 | "merged": false, 129 | "protected": true, 130 | "developers_can_push": false, 131 | "developers_can_merge": false, 132 | "can_push": false 133 | }, 134 | { 135 | "name": "1-2-stable", 136 | "commit": { 137 | "id": "b975f25a8661bdb0648448df1dcef6cd08994d49", 138 | "short_id": "b975f25a", 139 | "title": "Update CHANGELOG", 140 | "created_at": "2016-06-08T20:55:32.000+00:00", 141 | "parent_ids": null, 142 | "message": "Update CHANGELOG", 143 | "author_name": "Tomasz Maczukin", 144 | "author_email": "tomasz@maczukin.pl", 145 | "authored_date": "2016-06-08T20:55:32.000+00:00", 146 | "committer_name": "Tomasz Maczukin", 147 | "committer_email": "tomasz@maczukin.pl", 148 | "committed_date": "2016-06-08T20:55:32.000+00:00" 149 | }, 150 | "merged": false, 151 | "protected": true, 152 | "developers_can_push": false, 153 | "developers_can_merge": false, 154 | "can_push": false 155 | }, 156 | { 157 | "name": "1-3-stable", 158 | "commit": { 159 | "id": "3af07be2ae4f8bd289de2c1fdaafc800842de021", 160 | "short_id": "3af07be2", 161 | "title": "Update VERSION to 1.3.5", 162 | "created_at": "2016-09-13T12:39:38.000+00:00", 163 | "parent_ids": null, 164 | "message": "Update VERSION to 1.3.5", 165 | "author_name": "Tomasz Maczukin", 166 | "author_email": "tomasz@maczukin.pl", 167 | "authored_date": "2016-09-13T12:39:38.000+00:00", 168 | "committer_name": "Tomasz Maczukin", 169 | "committer_email": "tomasz@maczukin.pl", 170 | "committed_date": "2016-09-13T12:39:38.000+00:00" 171 | }, 172 | "merged": false, 173 | "protected": true, 174 | "developers_can_push": false, 175 | "developers_can_merge": false, 176 | "can_push": false 177 | }, 178 | { 179 | "name": "1-4-stable", 180 | "commit": { 181 | "id": "6f15e0a75c3e0aaf3b2f38afcc7bad6a655a61d0", 182 | "short_id": "6f15e0a7", 183 | "title": "Merge branch 'feature/changelog-entries-generator' into 'master'", 184 | "created_at": "2016-09-27T13:28:37.000+00:00", 185 | "parent_ids": null, 186 | "message": "Merge branch 'feature/changelog-entries-generator' into 'master'", 187 | "author_name": "Tomasz Maczukin", 188 | "author_email": "tomasz@gitlab.com", 189 | "authored_date": "2016-09-27T13:26:44.000+00:00", 190 | "committer_name": "Tomasz Maczukin", 191 | "committer_email": "tomasz@maczukin.pl", 192 | "committed_date": "2016-09-27T13:28:37.000+00:00" 193 | }, 194 | "merged": false, 195 | "protected": true, 196 | "developers_can_push": false, 197 | "developers_can_merge": false, 198 | "can_push": false 199 | }, 200 | { 201 | "name": "1-5-stable", 202 | "commit": { 203 | "id": "80d7eec767e633354ff4e51f46d2284ca7f578bd", 204 | "short_id": "80d7eec7", 205 | "title": "Merge branch 'feature/changelog-entries-generator' into 'master'", 206 | "created_at": "2016-09-27T13:28:21.000+00:00", 207 | "parent_ids": null, 208 | "message": "Merge branch 'feature/changelog-entries-generator' into 'master'", 209 | "author_name": "Tomasz Maczukin", 210 | "author_email": "tomasz@gitlab.com", 211 | "authored_date": "2016-09-27T13:26:44.000+00:00", 212 | "committer_name": "Tomasz Maczukin", 213 | "committer_email": "tomasz@maczukin.pl", 214 | "committed_date": "2016-09-27T13:28:21.000+00:00" 215 | }, 216 | "merged": false, 217 | "protected": true, 218 | "developers_can_push": false, 219 | "developers_can_merge": false, 220 | "can_push": false 221 | }, 222 | { 223 | "name": "1-6-stable", 224 | "commit": { 225 | "id": "765f5a595efcb6fd9c565d9e86f93839a7d0495f", 226 | "short_id": "765f5a59", 227 | "title": "Merge branch '1754-squash-no-tls-connection-state-warning' into 'master'", 228 | "created_at": "2016-10-13T13:04:28.000+00:00", 229 | "parent_ids": null, 230 | "message": "Merge branch '1754-squash-no-tls-connection-state-warning' into 'master'", 231 | "author_name": "Tomasz Maczukin", 232 | "author_email": "tomasz@gitlab.com", 233 | "authored_date": "2016-10-13T11:34:06.000+00:00", 234 | "committer_name": "Tomasz Maczukin", 235 | "committer_email": "tomasz@maczukin.pl", 236 | "committed_date": "2016-10-13T13:04:28.000+00:00" 237 | }, 238 | "merged": false, 239 | "protected": true, 240 | "developers_can_push": false, 241 | "developers_can_merge": false, 242 | "can_push": false 243 | }, 244 | { 245 | "name": "1-7-stable", 246 | "commit": { 247 | "id": "e9a6d506d02ba37bad8aafd3ca45919ea990c202", 248 | "short_id": "e9a6d506", 249 | "title": "Update CHANGELOG for v1.7.5", 250 | "created_at": "2017-01-21T14:11:43.000+00:00", 251 | "parent_ids": null, 252 | "message": "Update CHANGELOG for v1.7.5", 253 | "author_name": "Tomasz Maczukin", 254 | "author_email": "tomasz@maczukin.pl", 255 | "authored_date": "2017-01-21T14:11:43.000+00:00", 256 | "committer_name": "Tomasz Maczukin", 257 | "committer_email": "tomasz@maczukin.pl", 258 | "committed_date": "2017-01-21T14:11:43.000+00:00" 259 | }, 260 | "merged": false, 261 | "protected": true, 262 | "developers_can_push": false, 263 | "developers_can_merge": false, 264 | "can_push": false 265 | }, 266 | { 267 | "name": "1-8-stable", 268 | "commit": { 269 | "id": "ddb7a8169dc6425ff8f3c591f23e6dcf961e7f82", 270 | "short_id": "ddb7a816", 271 | "title": "Update CHANGELOG for v1.8.8", 272 | "created_at": "2017-02-21T23:03:32.000+00:00", 273 | "parent_ids": null, 274 | "message": "Update CHANGELOG for v1.8.8", 275 | "author_name": "Tomasz Maczukin", 276 | "author_email": "tomasz@maczukin.pl", 277 | "authored_date": "2017-02-21T23:03:32.000+00:00", 278 | "committer_name": "Tomasz Maczukin", 279 | "committer_email": "tomasz@maczukin.pl", 280 | "committed_date": "2017-02-21T23:03:32.000+00:00" 281 | }, 282 | "merged": false, 283 | "protected": true, 284 | "developers_can_push": false, 285 | "developers_can_merge": false, 286 | "can_push": false 287 | }, 288 | { 289 | "name": "1-9-stable", 290 | "commit": { 291 | "id": "79b5c463af1e31140245a36cf41b149a36b64c1d", 292 | "short_id": "79b5c463", 293 | "title": "Update CHANGELOG for v1.9.10", 294 | "created_at": "2017-03-22T23:14:36.000+00:00", 295 | "parent_ids": null, 296 | "message": "Update CHANGELOG for v1.9.10", 297 | "author_name": "Tomasz Maczukin", 298 | "author_email": "tomasz@maczukin.pl", 299 | "authored_date": "2017-03-22T23:14:36.000+00:00", 300 | "committer_name": "Tomasz Maczukin", 301 | "committer_email": "tomasz@maczukin.pl", 302 | "committed_date": "2017-03-22T23:14:36.000+00:00" 303 | }, 304 | "merged": false, 305 | "protected": true, 306 | "developers_can_push": false, 307 | "developers_can_merge": false, 308 | "can_push": false 309 | }, 310 | { 311 | "name": "10-0-stable", 312 | "commit": { 313 | "id": "a5c0deb1808c1d4a597512c9b20176ac3414a27c", 314 | "short_id": "a5c0deb1", 315 | "title": "Merge branch 'update-changelog' into 'master'", 316 | "created_at": "2017-10-09T12:48:21.000+00:00", 317 | "parent_ids": null, 318 | "message": "Merge branch 'update-changelog' into 'master'", 319 | "author_name": "Tomasz Maczukin", 320 | "author_email": "tomasz@gitlab.com", 321 | "authored_date": "2017-10-09T12:15:16.000+00:00", 322 | "committer_name": "Alessio Caiazza", 323 | "committer_email": "acaiazza@gitlab.com", 324 | "committed_date": "2017-10-09T12:48:21.000+00:00" 325 | }, 326 | "merged": false, 327 | "protected": true, 328 | "developers_can_push": false, 329 | "developers_can_merge": false, 330 | "can_push": false 331 | }, 332 | { 333 | "name": "10-1-stable", 334 | "commit": { 335 | "id": "a0152c4c791f1cf306bf2283332222c4e22a7d0f", 336 | "short_id": "a0152c4c", 337 | "title": "Merge branch '10-1-stable-patch-1' into 10-1-stable", 338 | "created_at": "2018-01-22T08:47:57.000+00:00", 339 | "parent_ids": null, 340 | "message": "Merge branch '10-1-stable-patch-1' into 10-1-stable", 341 | "author_name": "Alessio Caiazza", 342 | "author_email": "acaiazza@gitlab.com", 343 | "authored_date": "2018-01-22T08:47:57.000+00:00", 344 | "committer_name": "Alessio Caiazza", 345 | "committer_email": "acaiazza@gitlab.com", 346 | "committed_date": "2018-01-22T08:47:57.000+00:00" 347 | }, 348 | "merged": true, 349 | "protected": true, 350 | "developers_can_push": false, 351 | "developers_can_merge": false, 352 | "can_push": false 353 | }, 354 | { 355 | "name": "10-2-stable", 356 | "commit": { 357 | "id": "f157dc9f57628676c11d9dc2970653be9830cc94", 358 | "short_id": "f157dc9f", 359 | "title": "Merge branch '10-2-stable-patch-1' into 10-2-stable", 360 | "created_at": "2018-01-22T08:54:33.000+00:00", 361 | "parent_ids": null, 362 | "message": "Merge branch '10-2-stable-patch-1' into 10-2-stable", 363 | "author_name": "Alessio Caiazza", 364 | "author_email": "acaiazza@gitlab.com", 365 | "authored_date": "2018-01-22T08:54:33.000+00:00", 366 | "committer_name": "Alessio Caiazza", 367 | "committer_email": "acaiazza@gitlab.com", 368 | "committed_date": "2018-01-22T08:54:33.000+00:00" 369 | }, 370 | "merged": true, 371 | "protected": true, 372 | "developers_can_push": false, 373 | "developers_can_merge": false, 374 | "can_push": false 375 | }, 376 | { 377 | "name": "10-3-stable", 378 | "commit": { 379 | "id": "35afcf653c5567d9be30bf197f62e68c0fa57fb6", 380 | "short_id": "35afcf65", 381 | "title": "Merge branch '10-3-stable-patch-1' into 10-3-stable", 382 | "created_at": "2018-01-22T09:00:41.000+00:00", 383 | "parent_ids": null, 384 | "message": "Merge branch '10-3-stable-patch-1' into 10-3-stable", 385 | "author_name": "Alessio Caiazza", 386 | "author_email": "acaiazza@gitlab.com", 387 | "authored_date": "2018-01-22T09:00:41.000+00:00", 388 | "committer_name": "Alessio Caiazza", 389 | "committer_email": "acaiazza@gitlab.com", 390 | "committed_date": "2018-01-22T09:00:41.000+00:00" 391 | }, 392 | "merged": true, 393 | "protected": true, 394 | "developers_can_push": false, 395 | "developers_can_merge": false, 396 | "can_push": false 397 | }, 398 | { 399 | "name": "10-4-stable", 400 | "commit": { 401 | "id": "857480b64b013d8edffaf70e4b7ea43ee1c66a9e", 402 | "short_id": "857480b6", 403 | "title": "Update CHANGELOG for v10.4.0", 404 | "created_at": "2018-01-22T09:13:25.000+00:00", 405 | "parent_ids": null, 406 | "message": "Update CHANGELOG for v10.4.0", 407 | "author_name": "Alessio Caiazza", 408 | "author_email": "acaiazza@gitlab.com", 409 | "authored_date": "2018-01-22T09:13:25.000+00:00", 410 | "committer_name": "Alessio Caiazza", 411 | "committer_email": "acaiazza@gitlab.com", 412 | "committed_date": "2018-01-22T09:13:25.000+00:00" 413 | }, 414 | "merged": true, 415 | "protected": true, 416 | "developers_can_push": false, 417 | "developers_can_merge": false, 418 | "can_push": false 419 | }, 420 | { 421 | "name": "10-5-stable", 422 | "commit": { 423 | "id": "80b03db9893ab1668ac601909c1a1bf29d476035", 424 | "short_id": "80b03db9", 425 | "title": "v10.5.0 changelog", 426 | "created_at": "2018-02-22T08:33:47.000+00:00", 427 | "parent_ids": null, 428 | "message": "v10.5.0 changelog", 429 | "author_name": "Alessio Caiazza", 430 | "author_email": "acaiazza@gitlab.com", 431 | "authored_date": "2018-02-22T08:33:47.000+00:00", 432 | "committer_name": "Alessio Caiazza", 433 | "committer_email": "acaiazza@gitlab.com", 434 | "committed_date": "2018-02-22T08:33:47.000+00:00" 435 | }, 436 | "merged": true, 437 | "protected": true, 438 | "developers_can_push": false, 439 | "developers_can_merge": false, 440 | "can_push": false 441 | } 442 | ] 443 | -------------------------------------------------------------------------------- /fixtures/project_events.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "project_id": 250833, 4 | "action_name": "commented on", 5 | "target_id": 90908242, 6 | "target_iid": 90908242, 7 | "target_type": "Note", 8 | "author_id": 502819, 9 | "target_title": "Allow configuration of protected runner at registration time", 10 | "created_at": "2018-07-27T22:14:44.591Z", 11 | "note": { 12 | "id": 90908242, 13 | "type": null, 14 | "body": "is there any work being done to enable being able to protect runner when registering?", 15 | "attachment": null, 16 | "author": { 17 | "id": 502819, 18 | "name": "Nicholas Colbert", 19 | "username": "45cali", 20 | "state": "active", 21 | "avatar_url": "https://secure.gravatar.com/avatar/f1dab07b9931dd9115bb04b1c5b0398f?s=80&d=identicon", 22 | "web_url": "https://gitlab.com/45cali" 23 | }, 24 | "created_at": "2018-07-27T22:14:43.713Z", 25 | "updated_at": "2018-07-27T22:14:43.713Z", 26 | "system": false, 27 | "noteable_id": 9962050, 28 | "noteable_type": "Issue", 29 | "resolvable": false, 30 | "noteable_iid": 3186 31 | }, 32 | "author": { 33 | "id": 502819, 34 | "name": "Nicholas Colbert", 35 | "username": "45cali", 36 | "state": "active", 37 | "avatar_url": "https://secure.gravatar.com/avatar/f1dab07b9931dd9115bb04b1c5b0398f?s=80&d=identicon", 38 | "web_url": "https://gitlab.com/45cali" 39 | }, 40 | "author_username": "45cali" 41 | }, 42 | { 43 | "project_id": 250833, 44 | "action_name": "commented on", 45 | "target_id": 90844549, 46 | "target_iid": 90844549, 47 | "target_type": "Note", 48 | "author_id": 1090814, 49 | "target_title": "WIP: Introduce GCS cache support", 50 | "created_at": "2018-07-27T17:24:54.661Z", 51 | "note": { 52 | "id": 90844549, 53 | "type": null, 54 | "body": "I'll be awesome if you can support both! for context, we have secret management uncoupled from the runners configuration, and being able to rotate secrets w/o having to parse them and add private key to runner config would be very nice for us (and probably not only for us).\n\nI'm :eyes: this thread and volunteer to test the gcs implementation in our setup, thanks for working on this!", 55 | "attachment": null, 56 | "author": { 57 | "id": 1090814, 58 | "name": "Ilya Frolov", 59 | "username": "ilyaf", 60 | "state": "active", 61 | "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/1090814/avatar.png", 62 | "web_url": "https://gitlab.com/ilyaf" 63 | }, 64 | "created_at": "2018-07-27T17:24:53.631Z", 65 | "updated_at": "2018-07-27T17:24:53.631Z", 66 | "system": false, 67 | "noteable_id": 14178363, 68 | "noteable_type": "MergeRequest", 69 | "resolvable": false, 70 | "noteable_iid": 968 71 | }, 72 | "author": { 73 | "id": 1090814, 74 | "name": "Ilya Frolov", 75 | "username": "ilyaf", 76 | "state": "active", 77 | "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/1090814/avatar.png", 78 | "web_url": "https://gitlab.com/ilyaf" 79 | }, 80 | "author_username": "ilyaf" 81 | }, 82 | { 83 | "project_id": 250833, 84 | "action_name": "commented on", 85 | "target_id": 90841224, 86 | "target_iid": 90841224, 87 | "target_type": "Note", 88 | "author_id": 215818, 89 | "target_title": "WIP: Introduce GCS cache support", 90 | "created_at": "2018-07-27T17:00:27.993Z", 91 | "note": { 92 | "id": 90841224, 93 | "type": null, 94 | "body": "Hey @ilyaf, thanks for the comment. I see a point of using the credentials file directly. But I still like to have possibility to specify credentials directly in the `config.toml` file - it's how we're supporting S3 and in some cases it's easier to have only one file to manage.\n\nI'll se if I'll be able to add support for both cases in a clean way :)", 95 | "attachment": null, 96 | "author": { 97 | "id": 215818, 98 | "name": "Tomasz Maczukin", 99 | "username": "tmaczukin", 100 | "state": "active", 101 | "avatar_url": "https://secure.gravatar.com/avatar/f3b0a936861194d8eb9411eabbdd03ee?s=80&d=identicon", 102 | "web_url": "https://gitlab.com/tmaczukin" 103 | }, 104 | "created_at": "2018-07-27T17:00:26.962Z", 105 | "updated_at": "2018-07-27T17:00:26.962Z", 106 | "system": false, 107 | "noteable_id": 14178363, 108 | "noteable_type": "MergeRequest", 109 | "resolvable": false, 110 | "noteable_iid": 968 111 | }, 112 | "author": { 113 | "id": 215818, 114 | "name": "Tomasz Maczukin", 115 | "username": "tmaczukin", 116 | "state": "active", 117 | "avatar_url": "https://secure.gravatar.com/avatar/f3b0a936861194d8eb9411eabbdd03ee?s=80&d=identicon", 118 | "web_url": "https://gitlab.com/tmaczukin" 119 | }, 120 | "author_username": "tmaczukin" 121 | }, 122 | { 123 | "project_id": 250833, 124 | "action_name": "commented on", 125 | "target_id": 90837730, 126 | "target_iid": 90837730, 127 | "target_type": "Note", 128 | "author_id": 1090814, 129 | "target_title": "WIP: Introduce GCS cache support", 130 | "created_at": "2018-07-27T16:35:05.486Z", 131 | "note": { 132 | "id": 90837730, 133 | "type": null, 134 | "body": "Hey @tmaczukin, is it possible to design it in a way that `[runners.cache.gcs]` section would contain just path to the json credentials file, instead of specifying the private key in the open? That would heavily simplify the deployments, as all what's needed would be just pass a proper `GOOGLE_APPLICATION_CREDENTIALS` env var to runner.", 135 | "attachment": null, 136 | "author": { 137 | "id": 1090814, 138 | "name": "Ilya Frolov", 139 | "username": "ilyaf", 140 | "state": "active", 141 | "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/1090814/avatar.png", 142 | "web_url": "https://gitlab.com/ilyaf" 143 | }, 144 | "created_at": "2018-07-27T16:35:04.419Z", 145 | "updated_at": "2018-07-27T16:35:04.419Z", 146 | "system": false, 147 | "noteable_id": 14178363, 148 | "noteable_type": "MergeRequest", 149 | "resolvable": false, 150 | "noteable_iid": 968 151 | }, 152 | "author": { 153 | "id": 1090814, 154 | "name": "Ilya Frolov", 155 | "username": "ilyaf", 156 | "state": "active", 157 | "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/1090814/avatar.png", 158 | "web_url": "https://gitlab.com/ilyaf" 159 | }, 160 | "author_username": "ilyaf" 161 | }, 162 | { 163 | "project_id": 250833, 164 | "action_name": "pushed to", 165 | "target_id": null, 166 | "target_iid": null, 167 | "target_type": null, 168 | "author_id": 215818, 169 | "target_title": null, 170 | "created_at": "2018-07-27T16:02:37.975Z", 171 | "author": { 172 | "id": 215818, 173 | "name": "Tomasz Maczukin", 174 | "username": "tmaczukin", 175 | "state": "active", 176 | "avatar_url": "https://secure.gravatar.com/avatar/f3b0a936861194d8eb9411eabbdd03ee?s=80&d=identicon", 177 | "web_url": "https://gitlab.com/tmaczukin" 178 | }, 179 | "push_data": { 180 | "commit_count": 1, 181 | "action": "pushed", 182 | "ref_type": "branch", 183 | "commit_from": "a9b496bca00301ab11ebd87e97daf364a7a90da1", 184 | "commit_to": "3a48000afeba330fa85a0d7d52cf915d81e6709b", 185 | "ref": "introduce-gcs-cache-support", 186 | "commit_title": "Update documentation" 187 | }, 188 | "author_username": "tmaczukin" 189 | }, 190 | { 191 | "project_id": 250833, 192 | "action_name": "pushed to", 193 | "target_id": null, 194 | "target_iid": null, 195 | "target_type": null, 196 | "author_id": 210928, 197 | "target_title": null, 198 | "created_at": "2018-07-27T15:58:54.747Z", 199 | "author": { 200 | "id": 210928, 201 | "name": "Steve Azzopardi", 202 | "username": "SteveAzz", 203 | "state": "active", 204 | "avatar_url": "https://secure.gravatar.com/avatar/4a5eeed7ec5c80642df9ee708bb496d7?s=80&d=identicon", 205 | "web_url": "https://gitlab.com/SteveAzz" 206 | }, 207 | "push_data": { 208 | "commit_count": 1, 209 | "action": "pushed", 210 | "ref_type": "branch", 211 | "commit_from": "7b5edf3a881958e2c3b25689b4fc1101cb847a48", 212 | "commit_to": "d592a7ce84ec88e9deb3d63d5433386e341254e8", 213 | "ref": "25990-ci-web-terminal-2", 214 | "commit_title": "WIP: Extract session to pacakge" 215 | }, 216 | "author_username": "SteveAzz" 217 | }, 218 | { 219 | "project_id": 250833, 220 | "action_name": "commented on", 221 | "target_id": 90830480, 222 | "target_iid": 90830480, 223 | "target_type": "Note", 224 | "author_id": 2625287, 225 | "target_title": "Pending builds never started", 226 | "created_at": "2018-07-27T15:53:18.721Z", 227 | "note": { 228 | "id": 90830480, 229 | "type": null, 230 | "body": "We are encountering the same issue, but only on OSX runners. I think the root of problem stems from 1) using the hosted gitlab.com site and 2) there being no way to look at errors originating from our jobs in the production.log.\n\nMaybe there is a meaningful error or 2 in there, but no way for us to see it.", 231 | "attachment": null, 232 | "author": { 233 | "id": 2625287, 234 | "name": "earonesty", 235 | "username": "earonesty", 236 | "state": "active", 237 | "avatar_url": "https://secure.gravatar.com/avatar/803e178f33b9d1244688d2ab458db4d0?s=80&d=identicon", 238 | "web_url": "https://gitlab.com/earonesty" 239 | }, 240 | "created_at": "2018-07-27T15:53:17.847Z", 241 | "updated_at": "2018-07-27T15:53:17.847Z", 242 | "system": false, 243 | "noteable_id": 12392889, 244 | "noteable_type": "Issue", 245 | "resolvable": false, 246 | "noteable_iid": 3386 247 | }, 248 | "author": { 249 | "id": 2625287, 250 | "name": "earonesty", 251 | "username": "earonesty", 252 | "state": "active", 253 | "avatar_url": "https://secure.gravatar.com/avatar/803e178f33b9d1244688d2ab458db4d0?s=80&d=identicon", 254 | "web_url": "https://gitlab.com/earonesty" 255 | }, 256 | "author_username": "earonesty" 257 | }, 258 | { 259 | "project_id": 250833, 260 | "action_name": "opened", 261 | "target_id": 13041641, 262 | "target_iid": 3455, 263 | "target_type": "Issue", 264 | "author_id": 799868, 265 | "target_title": "after script should have information about job result", 266 | "created_at": "2018-07-27T15:42:26.594Z", 267 | "author": { 268 | "id": 799868, 269 | "name": "Maxim Ivanov", 270 | "username": "redbaron1", 271 | "state": "active", 272 | "avatar_url": "https://secure.gravatar.com/avatar/6d1f1e1de9801fb32a24fb3582be1c43?s=80&d=identicon", 273 | "web_url": "https://gitlab.com/redbaron1" 274 | }, 275 | "author_username": "redbaron1" 276 | }, 277 | { 278 | "project_id": 250833, 279 | "action_name": "opened", 280 | "target_id": 13041428, 281 | "target_iid": 3454, 282 | "target_type": "Issue", 283 | "author_id": 14583, 284 | "target_title": "Gitlab Runner on MacOSX: shell not found", 285 | "created_at": "2018-07-27T15:27:42.625Z", 286 | "author": { 287 | "id": 14583, 288 | "name": "Alfredo Palhares", 289 | "username": "masterkorp", 290 | "state": "active", 291 | "avatar_url": "https://secure.gravatar.com/avatar/7954f6f6be4ad94bcd19243bb6873fb4?s=80&d=identicon", 292 | "web_url": "https://gitlab.com/masterkorp" 293 | }, 294 | "author_username": "masterkorp" 295 | }, 296 | { 297 | "project_id": 250833, 298 | "action_name": "commented on", 299 | "target_id": 90777206, 300 | "target_iid": 90777206, 301 | "target_type": "Note", 302 | "author_id": 799868, 303 | "target_title": "Bad variable evaluation for dynamic environment url", 304 | "created_at": "2018-07-27T12:05:52.716Z", 305 | "note": { 306 | "id": 90777206, 307 | "type": null, 308 | "body": "oh, I see. Reason it is happening is because it evaluates all vars only once, it is not recursive. So it expanded `CI_ENVIRONMENT_URL=$COMPOSE_PROJECT_NAME` into \n\n`https://$DOCKER_PROJECT_NAME-$CI_COMMIT_REF_NAME.rms-dev.ucalgary.ca/` and then lowercased it since it is an URL. IMHO variable expansion worked as intended in this case.", 309 | "attachment": null, 310 | "author": { 311 | "id": 799868, 312 | "name": "Maxim Ivanov", 313 | "username": "redbaron1", 314 | "state": "active", 315 | "avatar_url": "https://secure.gravatar.com/avatar/6d1f1e1de9801fb32a24fb3582be1c43?s=80&d=identicon", 316 | "web_url": "https://gitlab.com/redbaron1" 317 | }, 318 | "created_at": "2018-07-27T12:05:51.448Z", 319 | "updated_at": "2018-07-27T12:05:51.448Z", 320 | "system": false, 321 | "noteable_id": 5580395, 322 | "noteable_type": "Issue", 323 | "resolvable": false, 324 | "noteable_iid": 2487 325 | }, 326 | "author": { 327 | "id": 799868, 328 | "name": "Maxim Ivanov", 329 | "username": "redbaron1", 330 | "state": "active", 331 | "avatar_url": "https://secure.gravatar.com/avatar/6d1f1e1de9801fb32a24fb3582be1c43?s=80&d=identicon", 332 | "web_url": "https://gitlab.com/redbaron1" 333 | }, 334 | "author_username": "redbaron1" 335 | }, 336 | { 337 | "project_id": 250833, 338 | "action_name": "opened", 339 | "target_id": 13035609, 340 | "target_iid": 3453, 341 | "target_type": "Issue", 342 | "author_id": 1579467, 343 | "target_title": "Is it possible to build locally on an autoscaled Docker container?", 344 | "created_at": "2018-07-27T10:14:08.756Z", 345 | "author": { 346 | "id": 1579467, 347 | "name": "Lawrence", 348 | "username": "SurferL", 349 | "state": "active", 350 | "avatar_url": "https://secure.gravatar.com/avatar/4caf24e4004c23b9032aab1d75d8fbdb?s=80&d=identicon", 351 | "web_url": "https://gitlab.com/SurferL" 352 | }, 353 | "author_username": "SurferL" 354 | }, 355 | { 356 | "project_id": 250833, 357 | "action_name": "opened", 358 | "target_id": 13033972, 359 | "target_iid": 3452, 360 | "target_type": "Issue", 361 | "author_id": 2630967, 362 | "target_title": "Building gitlab-runner source code on ppc64le", 363 | "created_at": "2018-07-27T08:58:21.752Z", 364 | "author": { 365 | "id": 2630967, 366 | "name": "Anita Nayak", 367 | "username": "Anita-Ibm", 368 | "state": "active", 369 | "avatar_url": "https://secure.gravatar.com/avatar/07f0fe66760da4fc0b2a4fcb087b054d?s=80&d=identicon", 370 | "web_url": "https://gitlab.com/Anita-Ibm" 371 | }, 372 | "author_username": "Anita-Ibm" 373 | }, 374 | { 375 | "project_id": 250833, 376 | "action_name": "pushed to", 377 | "target_id": null, 378 | "target_iid": null, 379 | "target_type": null, 380 | "author_id": 215818, 381 | "target_title": null, 382 | "created_at": "2018-07-27T08:49:05.984Z", 383 | "author": { 384 | "id": 215818, 385 | "name": "Tomasz Maczukin", 386 | "username": "tmaczukin", 387 | "state": "active", 388 | "avatar_url": "https://secure.gravatar.com/avatar/f3b0a936861194d8eb9411eabbdd03ee?s=80&d=identicon", 389 | "web_url": "https://gitlab.com/tmaczukin" 390 | }, 391 | "push_data": { 392 | "commit_count": 2, 393 | "action": "pushed", 394 | "ref_type": "branch", 395 | "commit_from": "892bc764e6c695ee1ce5420a4dfa6bc3ff4feaf3", 396 | "commit_to": "1651e1f627ab76215a1f0a8e07c6154e789bd56a", 397 | "ref": "master", 398 | "commit_title": "Merge branch 'cc-update' into 'master'" 399 | }, 400 | "author_username": "tmaczukin" 401 | }, 402 | { 403 | "project_id": 250833, 404 | "action_name": "accepted", 405 | "target_id": 14434068, 406 | "target_iid": 972, 407 | "target_type": "MergeRequest", 408 | "author_id": 215818, 409 | "target_title": "Fix missing code_quality widget", 410 | "created_at": "2018-07-27T08:49:05.876Z", 411 | "author": { 412 | "id": 215818, 413 | "name": "Tomasz Maczukin", 414 | "username": "tmaczukin", 415 | "state": "active", 416 | "avatar_url": "https://secure.gravatar.com/avatar/f3b0a936861194d8eb9411eabbdd03ee?s=80&d=identicon", 417 | "web_url": "https://gitlab.com/tmaczukin" 418 | }, 419 | "author_username": "tmaczukin" 420 | }, 421 | { 422 | "project_id": 250833, 423 | "action_name": "commented on", 424 | "target_id": 90733448, 425 | "target_iid": 90733448, 426 | "target_type": "Note", 427 | "author_id": 215818, 428 | "target_title": "chmod is used instead of lchmod", 429 | "created_at": "2018-07-27T08:37:20.343Z", 430 | "note": { 431 | "id": 90733448, 432 | "type": null, 433 | "body": "@danielmd3000 Well, it would definitelly be better than it is now, where we have no point to start debugging.\n\nBut still, without replicating the problem locally I think it will be hard to debug this and find out what and where is failing. I really don't like an idea of cloning your private code to my machine, even if this would be made for test purpose and the code would be deleted after tests. This opens a box with legal problems that I'd like to not touch, as much as possible. That's why I've asked for a small node aplication that replicates the problem and that is not your private code - to get it to my machine and start digging around this.\n\nI need to have problematic code locally to debug what Runner does with it and when it fails. Having access to your GitLab instance only to look on the failing job will give me no new knowledge - I've seen the failure on the snippet that you've posted above. And with this we still don't know what's the problem.", 434 | "attachment": null, 435 | "author": { 436 | "id": 215818, 437 | "name": "Tomasz Maczukin", 438 | "username": "tmaczukin", 439 | "state": "active", 440 | "avatar_url": "https://secure.gravatar.com/avatar/f3b0a936861194d8eb9411eabbdd03ee?s=80&d=identicon", 441 | "web_url": "https://gitlab.com/tmaczukin" 442 | }, 443 | "created_at": "2018-07-27T08:37:19.339Z", 444 | "updated_at": "2018-07-27T08:37:19.339Z", 445 | "system": false, 446 | "noteable_id": 11986019, 447 | "noteable_type": "Issue", 448 | "resolvable": false, 449 | "noteable_iid": 3354 450 | }, 451 | "author": { 452 | "id": 215818, 453 | "name": "Tomasz Maczukin", 454 | "username": "tmaczukin", 455 | "state": "active", 456 | "avatar_url": "https://secure.gravatar.com/avatar/f3b0a936861194d8eb9411eabbdd03ee?s=80&d=identicon", 457 | "web_url": "https://gitlab.com/tmaczukin" 458 | }, 459 | "author_username": "tmaczukin" 460 | }, 461 | { 462 | "project_id": 250833, 463 | "action_name": "commented on", 464 | "target_id": 90729861, 465 | "target_iid": 90729861, 466 | "target_type": "Note", 467 | "author_id": 681550, 468 | "target_title": "Bad variable evaluation for dynamic environment url", 469 | "created_at": "2018-07-27T08:19:49.289Z", 470 | "note": { 471 | "id": 90729861, 472 | "type": null, 473 | "body": "The same happens with or without curly braces.", 474 | "attachment": null, 475 | "author": { 476 | "id": 681550, 477 | "name": "Jairo Llopis", 478 | "username": "yajoman", 479 | "state": "active", 480 | "avatar_url": "https://secure.gravatar.com/avatar/76340b30482bc6dc546d6a47e724c3d1?s=80&d=identicon", 481 | "web_url": "https://gitlab.com/yajoman" 482 | }, 483 | "created_at": "2018-07-27T08:19:48.119Z", 484 | "updated_at": "2018-07-27T08:19:48.119Z", 485 | "system": false, 486 | "noteable_id": 5580395, 487 | "noteable_type": "Issue", 488 | "resolvable": false, 489 | "noteable_iid": 2487 490 | }, 491 | "author": { 492 | "id": 681550, 493 | "name": "Jairo Llopis", 494 | "username": "yajoman", 495 | "state": "active", 496 | "avatar_url": "https://secure.gravatar.com/avatar/76340b30482bc6dc546d6a47e724c3d1?s=80&d=identicon", 497 | "web_url": "https://gitlab.com/yajoman" 498 | }, 499 | "author_username": "yajoman" 500 | }, 501 | { 502 | "project_id": 250833, 503 | "action_name": "opened", 504 | "target_id": 13032661, 505 | "target_iid": 3451, 506 | "target_type": "Issue", 507 | "author_id": 435475, 508 | "target_title": "Unable to rename temporary files", 509 | "created_at": "2018-07-27T07:56:01.980Z", 510 | "author": { 511 | "id": 435475, 512 | "name": "Martin Delille", 513 | "username": "MartinDelille", 514 | "state": "active", 515 | "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/435475/avatar.png", 516 | "web_url": "https://gitlab.com/MartinDelille" 517 | }, 518 | "author_username": "MartinDelille" 519 | }, 520 | { 521 | "project_id": 250833, 522 | "action_name": "commented on", 523 | "target_id": 90724972, 524 | "target_iid": 90724972, 525 | "target_type": "Note", 526 | "author_id": 263716, 527 | "target_title": "Fail on unknown vars", 528 | "created_at": "2018-07-27T07:55:44.439Z", 529 | "note": { 530 | "id": 90724972, 531 | "type": null, 532 | "body": "/cc @nolith", 533 | "attachment": null, 534 | "author": { 535 | "id": 263716, 536 | "name": "Grzegorz Bizon", 537 | "username": "grzesiek", 538 | "state": "active", 539 | "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/263716/avatar.jpeg", 540 | "web_url": "https://gitlab.com/grzesiek" 541 | }, 542 | "created_at": "2018-07-27T07:55:43.304Z", 543 | "updated_at": "2018-07-27T07:55:43.304Z", 544 | "system": false, 545 | "noteable_id": 14477564, 546 | "noteable_type": "MergeRequest", 547 | "resolvable": false, 548 | "noteable_iid": 975 549 | }, 550 | "author": { 551 | "id": 263716, 552 | "name": "Grzegorz Bizon", 553 | "username": "grzesiek", 554 | "state": "active", 555 | "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/263716/avatar.jpeg", 556 | "web_url": "https://gitlab.com/grzesiek" 557 | }, 558 | "author_username": "grzesiek" 559 | }, 560 | { 561 | "project_id": 250833, 562 | "action_name": "commented on", 563 | "target_id": 90710572, 564 | "target_iid": 90710572, 565 | "target_type": "Note", 566 | "author_id": 210928, 567 | "target_title": "Typo while launching a VM", 568 | "created_at": "2018-07-27T06:31:06.862Z", 569 | "note": { 570 | "id": 90710572, 571 | "type": null, 572 | "body": "Thank you for pointing it out @aceaudio!", 573 | "attachment": null, 574 | "author": { 575 | "id": 210928, 576 | "name": "Steve Azzopardi", 577 | "username": "SteveAzz", 578 | "state": "active", 579 | "avatar_url": "https://secure.gravatar.com/avatar/4a5eeed7ec5c80642df9ee708bb496d7?s=80&d=identicon", 580 | "web_url": "https://gitlab.com/SteveAzz" 581 | }, 582 | "created_at": "2018-07-27T06:31:05.926Z", 583 | "updated_at": "2018-07-27T06:31:05.926Z", 584 | "system": false, 585 | "noteable_id": 13021333, 586 | "noteable_type": "Issue", 587 | "resolvable": false, 588 | "noteable_iid": 3448 589 | }, 590 | "author": { 591 | "id": 210928, 592 | "name": "Steve Azzopardi", 593 | "username": "SteveAzz", 594 | "state": "active", 595 | "avatar_url": "https://secure.gravatar.com/avatar/4a5eeed7ec5c80642df9ee708bb496d7?s=80&d=identicon", 596 | "web_url": "https://gitlab.com/SteveAzz" 597 | }, 598 | "author_username": "SteveAzz" 599 | }, 600 | { 601 | "project_id": 250833, 602 | "action_name": "opened", 603 | "target_id": 13031185, 604 | "target_iid": 3450, 605 | "target_type": "Issue", 606 | "author_id": 210928, 607 | "target_title": "Update to dep 0.5", 608 | "created_at": "2018-07-27T06:26:55.945Z", 609 | "author": { 610 | "id": 210928, 611 | "name": "Steve Azzopardi", 612 | "username": "SteveAzz", 613 | "state": "active", 614 | "avatar_url": "https://secure.gravatar.com/avatar/4a5eeed7ec5c80642df9ee708bb496d7?s=80&d=identicon", 615 | "web_url": "https://gitlab.com/SteveAzz" 616 | }, 617 | "author_username": "SteveAzz" 618 | } 619 | ] -------------------------------------------------------------------------------- /test/components/__snapshots__/Branches.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should render as expected 1`] = ` 4 |
8 |
11 |
15 | 16 | 27 | Branches 28 |
31 | 42 32 |
33 |
34 |
37 | 48 | 54 | 59 | 64 | 67 | 68 |
69 |
70 |
74 |
77 |
84 | 117 |
121 | 125 | Fix 0.6.2 release 126 | 127 | 137 | 148 | 153 | 156 | 157 |   158 | 3 years ago 159 | 160 |
161 |
162 |
163 |
166 |
173 |
176 | 205 |
206 |
210 | 214 | Fix compilation error 215 | 216 | 226 | 237 | 242 | 245 | 246 |   247 | 3 years ago 248 | 249 |
250 |
251 |
252 |
255 |
262 |
265 | 294 |
295 |
299 | 303 | Fix error level checking for Windows Batch and PowerShell 304 | 305 | 315 | 326 | 331 | 334 | 335 |   336 | 2 years ago 337 | 338 |
339 |
340 |
341 |
344 |
351 |
354 | 383 |
384 |
388 | 392 | Add CI_BUILD_TOKEN 393 | 394 | 404 | 415 | 420 | 423 | 424 |   425 | 2 years ago 426 | 427 |
428 |
429 |
430 |
433 |
440 | 473 |
477 | 481 | Update CHANGELOG for v1.10.8 482 | 483 | 493 | 504 | 509 | 512 | 513 |   514 | a year ago 515 | 516 |
517 |
518 |
519 |
522 |
529 | 562 |
566 | 570 | Bump version to 1.11.6 571 | 572 | 582 | 593 | 598 | 601 | 602 |   603 | a year ago 604 | 605 |
606 |
607 |
608 |
611 |
618 |
621 | 650 |
651 |
655 | 659 | Update CHANGELOG 660 | 661 | 671 | 682 | 687 | 690 | 691 |   692 | 2 years ago 693 | 694 |
695 |
696 |
697 |
700 |
707 |
710 | 739 |
740 |
744 | 748 | Update VERSION to 1.3.5 749 | 750 | 760 | 771 | 776 | 779 | 780 |   781 | 2 years ago 782 | 783 |
784 |
785 |
786 |
789 |
796 |
799 | 828 |
829 |
833 | 837 | Merge branch 'feature/changelog-entries-generator' into 'master' 838 | 839 | 849 | 860 | 865 | 868 | 869 |   870 | 2 years ago 871 | 872 |
873 |
874 |
875 |
878 |
885 |
888 | 917 |
918 |
922 | 926 | Merge branch 'feature/changelog-entries-generator' into 'master' 927 | 928 | 938 | 949 | 954 | 957 | 958 |   959 | 2 years ago 960 | 961 |
962 |
963 |
964 |
967 |
974 |
977 | 1006 |
1007 |
1011 | 1015 | Merge branch '1754-squash-no-tls-connection-state-warning' into 'master' 1016 | 1017 | 1027 | 1038 | 1043 | 1046 | 1047 |   1048 | 2 years ago 1049 | 1050 |
1051 |
1052 |
1053 |
1056 |
1063 |
1066 | 1095 |
1096 |
1100 | 1104 | Update CHANGELOG for v1.7.5 1105 | 1106 | 1116 | 1127 | 1132 | 1135 | 1136 |   1137 | 2 years ago 1138 | 1139 |
1140 |
1141 |
1142 |
1145 |
1152 |
1155 | 1184 |
1185 |
1189 | 1193 | Update CHANGELOG for v1.8.8 1194 | 1195 | 1205 | 1216 | 1221 | 1224 | 1225 |   1226 | a year ago 1227 | 1228 |
1229 |
1230 |
1231 |
1234 |
1241 |
1244 | 1273 |
1274 |
1278 | 1282 | Update CHANGELOG for v1.9.10 1283 | 1284 | 1294 | 1305 | 1310 | 1313 | 1314 |   1315 | a year ago 1316 | 1317 |
1318 |
1319 |
1320 |
1323 |
1330 |
1333 | 1362 |
1363 |
1367 | 1371 | Merge branch 'update-changelog' into 'master' 1372 | 1373 | 1383 | 1394 | 1399 | 1402 | 1403 |   1404 | 10 months ago 1405 | 1406 |
1407 |
1408 |
1409 |
1412 |
1419 |
1422 | 1451 |
1452 |
1456 | 1460 | Merge branch '10-1-stable-patch-1' into 10-1-stable 1461 | 1462 | 1472 | 1483 | 1488 | 1491 | 1492 |   1493 | 6 months ago 1494 | 1495 |
1496 |
1497 |
1498 |
1501 |
1508 |
1511 | 1540 |
1541 |
1545 | 1549 | Merge branch '10-2-stable-patch-1' into 10-2-stable 1550 | 1551 | 1561 | 1572 | 1577 | 1580 | 1581 |   1582 | 6 months ago 1583 | 1584 |
1585 |
1586 |
1587 |
1590 |
1597 |
1600 | 1629 |
1630 |
1634 | 1638 | Merge branch '10-3-stable-patch-1' into 10-3-stable 1639 | 1640 | 1650 | 1661 | 1666 | 1669 | 1670 |   1671 | 6 months ago 1672 | 1673 |
1674 |
1675 |
1676 |
1679 |
1686 |
1689 | 1718 |
1719 |
1723 | 1727 | Update CHANGELOG for v10.4.0 1728 | 1729 | 1739 | 1750 | 1755 | 1758 | 1759 |   1760 | 6 months ago 1761 | 1762 |
1763 |
1764 |
1765 |
1768 |
1775 |
1778 | 1807 |
1808 |
1812 | 1816 | v10.5.0 changelog 1817 | 1818 | 1828 | 1839 | 1844 | 1847 | 1848 |   1849 | 5 months ago 1850 | 1851 |
1852 |
1853 |
1854 |
1855 |
1856 |
1857 | `; 1858 | --------------------------------------------------------------------------------