Skip to content

Commit

Permalink
Support cocoapods search on harvest page
Browse files Browse the repository at this point in the history
  • Loading branch information
qtomlinson committed Apr 6, 2022
1 parent 0ad352b commit 25d9d00
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 4 deletions.
12 changes: 11 additions & 1 deletion src/api/clearlyDefined.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const ORIGINS_PYPI = 'origins/pypi'
export const ORIGINS_RUBYGEMS = 'origins/rubygems'
export const ORIGINS_DEBIAN = 'origins/deb'
export const ORIGINS_COMPOSER = 'origins/composer'
export const ORIGINS_POD = 'origins/pod'
export const ORIGINS = {
github: { git: ORIGINS_GITHUB },
npmjs: { npm: ORIGINS_NPM },
Expand All @@ -32,7 +33,8 @@ export const ORIGINS = {
pypi: { pypi: ORIGINS_PYPI },
rubygems: { gem: ORIGINS_RUBYGEMS },
debian: { deb: ORIGINS_DEBIAN, debsrc: ORIGINS_DEBIAN },
packagist: { composer: ORIGINS_COMPOSER }
packagist: { composer: ORIGINS_COMPOSER },
cocoapods: { pod: ORIGINS_POD }
}

export function getHarvestResults(token, entity) {
Expand Down Expand Up @@ -218,6 +220,14 @@ export function getComposerRevisions(token, path) {
return get(url(`${ORIGINS_COMPOSER}/${path}/revisions`), token)
}

export function getCocoaPodsSearch(token, path) {
return get(url(`${ORIGINS_POD}/${path}`), token)
}

export function getCocoaPodsRevisions(token, path) {
return get(url(`${ORIGINS_POD}/${path}/revisions`), token)
}

export function getRevisions(token, path, type, provider) {
const origin = _.get(ORIGINS, `${provider}.${type}`)
return get(url(`${origin}/${path}/revisions`), token)
Expand Down
66 changes: 66 additions & 0 deletions src/components/CocoaPodsSelector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// (c) Copyright 2022, SAP SE and ClearlyDefined contributors. Licensed under the MIT license.
// SPDX-License-Identifier: MIT

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { getCocoaPodsSearch } from '../api/clearlyDefined'
import { AsyncTypeahead } from 'react-bootstrap-typeahead'
import searchSvg from '../images/icons/searchSvg.svg'
import 'react-bootstrap-typeahead/css/Typeahead.css'

export default class CocoaPodsSelector extends Component {
static propTypes = {
onChange: PropTypes.func
}

constructor(props) {
super(props)
this.state = { isLoading: false, options: [], focus: true }
this.getOptions = this.getOptions.bind(this)
this.onChange = this.onChange.bind(this)
}

onChange(values) {
const { onChange } = this.props
const value = values.length === 0 ? null : values[0]
value && onChange && onChange({ type: 'pod', provider: 'cocoapods', name: value.id }, 'package')
}

async getOptions(value) {
try {
this.setState({ ...this.state, isLoading: true })
const options = await getCocoaPodsSearch(this.props.token, value)
this.setState({ ...this.state, options, isLoading: false })
} catch (error) {
this.setState({ ...this.state, options: [], isLoading: false })
}
}

render() {
const { options, isLoading, focus } = this.state
return (
<div className={`harvest-searchbar ${focus ? 'active' : ''}`}>
<div className="search-logo">
<img src={searchSvg} alt="search" />
</div>
<AsyncTypeahead
id="pod-selector"
className="harvest-search"
useCache={false}
options={options}
placeholder={'Pick a CocoaPod to harvest'}
onChange={this.onChange}
labelKey="id"
onFocus={() => this.setState({ ...this.state, focus: true })}
onBlur={() => this.setState({ ...this.state, focus: false })}
clearButton
highlightOnlyResult
emptyLabel=""
selectHintOnEnter
isLoading={isLoading}
onSearch={this.getOptions}
/>
</div>
)
}
}
95 changes: 95 additions & 0 deletions src/components/CocoaPodsVersionPicker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// (c) Copyright 2022, SAP SE and ClearlyDefined contributors. Licensed under the MIT license.
// SPDX-License-Identifier: MIT

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { getCocoaPodsRevisions } from '../api/clearlyDefined'
import Autocomplete from './Navigation/Ui/Autocomplete'
import searchSvg from '../images/icons/searchSvg.svg'

export default class CocoaPodsVersionPicker extends Component {
static propTypes = {
onChange: PropTypes.func,
request: PropTypes.object.isRequired,
defaultInputValue: PropTypes.string
}

constructor(props) {
super(props)
this.state = {
customValues: [],
options: [],
focus: false,
selected: props.request.revision ? [props.request.revision] : []
}
this.onChange = this.onChange.bind(this)
this.filter = this.filter.bind(this)
}

componentDidMount() {
this.getOptions('')
}

componentWillReceiveProps(nextProps) {
this.setState({ ...this.state, selected: nextProps.request.revision ? [nextProps.request.revision] : [] })
}

async getOptions(value) {
try {
const { name } = this.props.request
const options = await getCocoaPodsRevisions(this.props.token, name)
this.setState({ ...this.state, options })
} catch (error) {
this.setState({ ...this.state, options: [] })
}
}

onChange(values) {
const { onChange } = this.props
if (!onChange) return
let value = values.length === 0 ? null : values[0]
if (!value) return onChange(value)
if (value.customOption) {
value = value.label
this.setState({ ...this.state, customValues: [...this.state.customValues, value] })
}
onChange(value)
}

filter(option, props) {
if (this.props.request.revision) return true
return option.toLowerCase().indexOf(props.text.toLowerCase()) !== -1
}

render() {
const { defaultInputValue } = this.props
const { customValues, options, selected, focus } = this.state
const list = customValues.concat(options)
return (
<div className={`harvest-searchbar ${focus ? 'active' : ''}`}>
<div className="search-logo">
<img src={searchSvg} alt="search" />
</div>{' '}
<Autocomplete
id="cocoapods-version-picker"
selected={selected}
options={list}
defaultInputValue={defaultInputValue}
placeholder={
options.length === 0 ? 'Could not fetch versions, type a CocoaPod version' : 'Pick a CocoaPod version'
}
onChange={this.onChange}
onFocus={() => this.setState({ ...this.state, focus: true })}
onBlur={() => this.setState({ ...this.state, focus: false })}
positionFixed
clearButton
allowNew
newSelectionPrefix="Version:"
emptyLabel=""
filterBy={this.filter}
selectHintOnEnter
/>
</div>
)
}
}
12 changes: 9 additions & 3 deletions src/components/HarvestQueueList.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
DebianVersionPicker,
NuGetVersionPicker,
RubyGemsVersionPicker,
ComposerVersionPicker
ComposerVersionPicker,
CocoaPodsVersionPicker
} from './'
import { getGitHubRevisions } from '../api/clearlyDefined'
import { clone } from 'lodash'
Expand All @@ -28,6 +29,7 @@ import cargo from '../images/cargo.png'
import maven from '../images/maven.png'
import nuget from '../images/nuget.png'
import composer from '../images/packagist.png'
import pod from '../images/pod.png'

class HarvestQueueList extends React.Component {
static propTypes = {
Expand All @@ -39,7 +41,7 @@ class HarvestQueueList extends React.Component {
}

static defaultProps = {
loadMoreRows: () => {}
loadMoreRows: () => { }
}

constructor(props) {
Expand Down Expand Up @@ -106,6 +108,9 @@ class HarvestQueueList extends React.Component {
{request.provider === 'packagist' && (
<ComposerVersionPicker request={request} onChange={this.versionChanged.bind(this, request)} />
)}
{request.provider === 'cocoapods' && (
<CocoaPodsVersionPicker request={request} onChange={this.versionChanged.bind(this, request)} />
)}
<i className="fas fa-times list-trash" onClick={this.removeRequest.bind(this, request)} />
</div>
)
Expand Down Expand Up @@ -150,6 +155,7 @@ class HarvestQueueList extends React.Component {
if (request.provider === 'nuget') return nuget
if (request.provider === 'debian') return debian
if (request.provider === 'packagist') return composer
if (request.provider === 'cocoapods') return pod
return null
}

Expand All @@ -161,7 +167,7 @@ class HarvestQueueList extends React.Component {
renderRow({ index, key, style }) {
const { list } = this.props
const request = list[index]
const clickHandler = () => {}
const clickHandler = () => { }
return (
<div key={key} style={style} className="component-row">
<TwoLineEntry
Expand Down
2 changes: 2 additions & 0 deletions src/components/Navigation/Pages/PageHarvest/PageHarvest.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ComposerSelector,
PyPiSelector,
RubyGemsSelector,
CocoaPodsSelector,
Section
} from '../../../'
import { uiNavigation, uiHarvestUpdateQueue, uiNotificationNew } from '../../../../actions/ui'
Expand Down Expand Up @@ -122,6 +123,7 @@ class PageHarvest extends Component {
{activeProvider.value === 'pypi' && <PyPiSelector onChange={this.onAddRequest} />}
{activeProvider.value === 'rubygems' && <RubyGemsSelector onChange={this.onAddRequest} />}
{activeProvider.value === 'debian' && <DebianSelector onChange={this.onAddRequest} />}
{activeProvider.value === 'cocoapods' && <CocoaPodsSelector onChange={this.onAddRequest} />}
</Col>
<Col md={2} className="harvest-action">
{this.renderActionButton()}
Expand Down
4 changes: 4 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export { default as RubyGemsSelector }
from './RubyGemsSelector'
export { default as RubyGemsVersionPicker }
from './RubyGemsVersionPicker'
export { default as CocoaPodsSelector }
from './CocoaPodsSelector'
export { default as CocoaPodsVersionPicker }
from './CocoaPodsVersionPicker'
export { default as PageAbout }
from './PageAbout'
export { default as RehydrationProvider }
Expand Down

0 comments on commit 25d9d00

Please sign in to comment.