Skip to content

Commit

Permalink
#264 Add initial GitLab support
Browse files Browse the repository at this point in the history
  • Loading branch information
joejag committed Jan 9, 2019
1 parent 63bc5ce commit 9aa32ea
Show file tree
Hide file tree
Showing 20 changed files with 542 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/client/actions/Actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ export const EXPORT_SUCCESS = 'EXPORT_SUCCESS'
export const EXPORT_ERROR = 'EXPORT_ERROR'
export const GITHUB_SET_GIST_ID = 'GITHUB_SET_GIST_ID'
export const GITHUB_SET_DESCRIPTION = 'GITHUB_SET_DESCRIPTION'
export const GITLAB_SET_SNIPPET_ID = 'GITLAB_SET_SNIPPET_ID'
export const GITLAB_SET_TITLE = 'GITLAB_SET_TITLE'
export const GITLAB_SET_URL = 'GITLAB_SET_URL'
export const IMPORTING = 'IMPORTING'
export const IMPORT_ERROR = 'IMPORT_ERROR'
export const IMPORT_SUCCESS = 'IMPORT_SUCCESS'
Expand Down
13 changes: 13 additions & 0 deletions src/client/actions/GitLabActionCreators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {GITLAB_SET_TITLE, GITLAB_SET_SNIPPET_ID, GITLAB_SET_URL} from './Actions'

export function gitLabSetUrl(url) {
return {type: GITLAB_SET_URL, url}
}

export function gitLabSetSnippetId(snippetId) {
return {type: GITLAB_SET_SNIPPET_ID, snippetId}
}

export function gitLabSetTitle(title) {
return {type: GITLAB_SET_TITLE, title}
}
76 changes: 76 additions & 0 deletions src/client/actions/GitLabThunkActionCreators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {exportError, exporting, exportSuccess} from './ExportActionCreators'
import {importError, importing} from './ImportActionCreators'
import {gitLabUrl, gitLabSnippetId, gitLabTitle} from '../reducers/Selectors'
import {toJson} from '../common/Json'
import {filter} from '../reducers/Configuration'
import {isBlank} from '../common/Utils'
import {gitLabSetSnippetId, gitLabSetTitle} from './GitLabActionCreators'
import {createSnippet, updateSnippet, getSnippetMeta, getSnippetContent, send} from '../gateways/GitLabGateway'
import {importData} from './ImportThunkActionCreators'

export function restoreFromGitLab(accessToken) {
return async (dispatch, getState) => {
dispatch(importing())

const url = gitLabUrl(getState())
const snippetId = gitLabSnippetId(getState())

if (isBlank(snippetId)) {
dispatch(importError(['You must provide a snippet ID to import from GitLab']))
} else {
try {
const resMeta = await send(getSnippetMeta(url, snippetId, accessToken))
await handleSnippetResponse(dispatch, resMeta)

const content = await send(getSnippetContent(url, snippetId, accessToken))
dispatch(importData(content))
} catch (error) {
dispatch(importError([`Unable to import from GitLab because of an error: ${error.message}`]))
}
}
}
}

function handleSnippetResponse(dispatch, res) {
const configuration = res.get('file_name')

if (configuration !== 'configuration.json') {
throw new Error('snippet does not contain the required configuration.json file')
} else {
dispatch(gitLabSetTitle(res.get('title')))
}
}

export function uploadToGitLab(accessToken) {
return async (dispatch, getState) => {
dispatch(exporting())

const url = gitLabUrl(getState())
const id = gitLabSnippetId(getState())
const title = gitLabTitle(getState())
const configuration = toJson(filter(getState().toJS()))

if (isBlank(accessToken)) {
dispatch(exportError(['You must provide an access token to upload to GitLab']))
} else {
const createNewSnippet = isBlank(id)

const req = createNewSnippet
? createSnippet(url, title, configuration, accessToken)
: updateSnippet(url, id, title, configuration, accessToken)

try {
const snippetJson = await send(req)
const returnedSnippetId = snippetJson.get('id').toString()
const successMessage = createNewSnippet
? `Successfully created snippet ${returnedSnippetId}`
: `Successfully updated snippet ${returnedSnippetId}`

dispatch(exportSuccess([successMessage]))
dispatch(gitLabSetSnippetId(returnedSnippetId))
} catch (error) {
dispatch(exportError([`Unable to upload to GitLab because of an error: ${error.message}`]))
}
}
}
}
43 changes: 43 additions & 0 deletions src/client/backup/GitLabSnippetInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Input} from '../common/forms/Input'
import styles from './snippet-id-input.scss'

export class GitLabSnippetInput extends Component {

constructor(props) {
super(props)
this.state = {
newSnippetId: props.snippetId
}
}

snippetIdChanged = (evt) => {
this.setState({newSnippetId: evt.target.value})
}

setSnippetId = () => {
this.props.setSnippetId(this.state.newSnippetId)
}

render() {
const {newSnippetId} = this.state
const {disabled} = this.props

return (
<Input className={styles.snippetId}
value={newSnippetId}
onChange={this.snippetIdChanged}
onBlur={this.setSnippetId}
disabled={disabled}>
<div className={styles.label}>snippet ID</div>
</Input>
)
}
}

GitLabSnippetInput.propTypes = {
snippetId: PropTypes.string.isRequired,
setSnippetId: PropTypes.func.isRequired,
disabled: PropTypes.bool
}
43 changes: 43 additions & 0 deletions src/client/backup/GitLabUrlInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Input} from '../common/forms/Input'
import styles from './snippet-id-input.scss'

export class GitLabUrlInput extends Component {

constructor(props) {
super(props)
this.state = {
newUrl: props.url
}
}

urlChanged = (evt) => {
this.setState({newUrl: evt.target.value})
}

setUrl = () => {
this.props.setUrl(this.state.newUrl)
}

render() {
const {newUrl} = this.state
const {disabled} = this.props

return (
<Input className={styles.srlId}
value={newUrl}
onChange={this.urlChanged}
onBlur={this.setUrl}
disabled={disabled}>
<div className={styles.label}>url</div>
</Input>
)
}
}

GitLabUrlInput.propTypes = {
url: PropTypes.string.isRequired,
setUrl: PropTypes.func.isRequired,
disabled: PropTypes.bool
}
4 changes: 3 additions & 1 deletion src/client/backup/export/Export.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import {Container} from '../../common/Container'
import {Messages} from '../../common/Messages'
import LocallyContainer from './locally/LocallyContainer'
import GitHubContainer from './github/GitHubContainer'
import GitLabContainer from './gitlab/GitLabContainer'
import {Tabs} from '../../common/Tabs'

export function Export({configuration, infos, errors}) {
return (
<Container title='Export'>
<Tabs titles={['locally', 'GitHub']}>
<Tabs titles={['locally', 'GitHub', 'GitLab']}>
<LocallyContainer configuration={configuration}/>
<GitHubContainer/>
<GitLabContainer/>
</Tabs>
<Messages type='error' messages={errors}/>
<Messages type='info' messages={infos}/>
Expand Down
88 changes: 88 additions & 0 deletions src/client/backup/export/gitlab/GitLab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, {Component, Fragment} from 'react'
import PropTypes from 'prop-types'
import {Input} from '../../../common/forms/Input'
import {GitLabSnippetInput} from '../../GitLabSnippetInput'
import {GitLabUrlInput} from '../../GitLabUrlInput'
import {PrimaryButton} from '../../../common/forms/Button'
import {iCloudUpload} from '../../../common/fonts/Icons'
import {Password} from '../../../common/forms/Password'
import styles from './gitlab.scss'

export class GitLab extends Component {

constructor(props) {
super(props)
this.state = {
accessToken: '',
newTitle: props.title
}
}

accessTokenChanged = (evt) => {
this.setState({accessToken: evt.target.value})
}

titleChanged = (evt) => {
this.setState({newTitle: evt.target.value})
}

setTitle = () => {
this.props.gitLabSetTitle(this.state.newTitle)
}

upload = () => {
const {accessToken} = this.state
this.props.uploadToGitLab(accessToken)
}

render() {
const {loaded, snippetId, url, gitLabSetSnippetId, gitLabSetUrl} = this.props
const {accessToken, newTitle} = this.state
const disabled = !loaded

return (
<Fragment>
<GitLabUrlInput key={url}
url={url}
setUrl={gitLabSetUrl}
disabled={disabled}>
</GitLabUrlInput>
<GitLabSnippetInput key={snippetId}
snippetId={snippetId}
setSnippetId={gitLabSetSnippetId}
disabled={disabled}/>
<Input value={newTitle}
onChange={this.titleChanged}
onBlur={this.setTitle}
disabled={disabled}
maxLength='256'>
<div className={styles.label}>title</div>
</Input>
<Password className={styles.accessToken}
onChange={this.accessTokenChanged}
onBlur={this.accessTokenChanged}
value={accessToken}
disabled={disabled}>
<div className={styles.label}>access token</div>
</Password>
<PrimaryButton className={styles.export}
onClick={this.upload}
disabled={disabled}
icon={iCloudUpload}>
export
</PrimaryButton>
</Fragment>
)
}
}

GitLab.propTypes = {
loaded: PropTypes.bool,
uploadToGitLab: PropTypes.func.isRequired,
gitLabSetSnippetId: PropTypes.func.isRequired,
gitLabSetTitle: PropTypes.func.isRequired,
gitLabSetUrl:PropTypes.func.isRequired,
snippetId: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
title: PropTypes.string.isRequired
}
27 changes: 27 additions & 0 deletions src/client/backup/export/gitlab/GitLabContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {GitLab} from './GitLab'
import {uploadToGitLab} from '../../../actions/GitLabThunkActionCreators'
import {gitLabSetTitle, gitLabSetUrl, gitLabSetSnippetId} from '../../../actions/GitLabActionCreators'
import {gitLabUrl, gitLabSnippetId, gitLabTitle, importLoaded} from '../../../reducers/Selectors'

function mapDispatchToProps(dispatch) {
return bindActionCreators({
uploadToGitLab,
gitLabSetTitle,
gitLabSetUrl,
gitLabSetSnippetId
}, dispatch)
}


function mapStateToProps(state) {
return {
loaded: importLoaded(state),
url: gitLabUrl(state),
snippetId: gitLabSnippetId(state),
title: gitLabTitle(state)
}
}

export default connect(mapStateToProps, mapDispatchToProps)(GitLab)
22 changes: 22 additions & 0 deletions src/client/backup/export/gitlab/gitlab.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@import '../../../common/variables';
@import '../../../common/layout';

$label-size: 6em;

.export {
@extend %blocking;
}

.label {
width: $label-size;
}

.accessToken {
@include respond-to(tablet, desktop) {
width: 36em;
}
}

.help {
margin: 0 $margin-right 0 0;
}
4 changes: 3 additions & 1 deletion src/client/backup/import/Import.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import {Container} from '../../common/Container'
import {Messages} from '../../common/Messages'
import LocallyContainer from './locally/LocallyContainer'
import GitHubContainer from './github/GitHubContainer'
import GitLabContainer from './gitlab/GitLabContainer'
import {Tabs} from '../../common/Tabs'

export function Import({infos, errors}) {
return (
<Container title='Import'>
<Tabs titles={['locally', 'GitHub']}>
<Tabs titles={['locally', 'GitHub', 'GitLab']}>
<LocallyContainer/>
<GitHubContainer/>
<GitLabContainer/>
</Tabs>
<Messages type='error' messages={errors}/>
<Messages type='info' messages={infos}/>
Expand Down
Loading

4 comments on commit 9aa32ea

@GentlemanHal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, adding the extra tab for Gitlab makes the tabs overflow the container on my mobile (One Plus 2)

@joejag
Copy link
Contributor Author

@joejag joejag commented on 9aa32ea Jan 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for testing. I'll take a look

@GentlemanHal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if its better to combine the tabs into "remotely" and have a drop down where you select github or gitlab?

@joejag
Copy link
Contributor Author

@joejag joejag commented on 9aa32ea Jan 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but are we doing that because it's a better UI layout or to get around fixing the overflow of tabs? :)

Please sign in to comment.