Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Conda Support #1028

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/api/clearlyDefined.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const BROWSE = 'browse'
export const ORIGINS_GITHUB = 'origins/github'
export const ORIGINS_NPM = 'origins/npm'
export const ORIGINS_NUGET = 'origins/nuget'
export const ORIGINS_CONDA = 'origins/conda'
export const ORIGINS_CRATE = 'origins/crate'
export const ORIGINS_MAVEN = 'origins/maven'
export const ORIGINS_PYPI = 'origins/pypi'
Expand All @@ -25,9 +26,12 @@ export const ORIGINS_DEBIAN = 'origins/deb'
export const ORIGINS_COMPOSER = 'origins/composer'
export const ORIGINS_POD = 'origins/pod'
export const ORIGINS = {
"anaconda-main": { conda: ORIGINS_CONDA, condasrc: ORIGINS_CONDA },
"anaconda-r": { conda: ORIGINS_CONDA, condasrc: ORIGINS_CONDA },
github: { git: ORIGINS_GITHUB },
npmjs: { npm: ORIGINS_NPM },
nuget: { nuget: ORIGINS_NUGET },
"conda-forge": { conda: ORIGINS_CONDA, condasrc: ORIGINS_CONDA },
cratesio: { crate: ORIGINS_CRATE },
mavencentral: { maven: ORIGINS_MAVEN, sourcearchive: ORIGINS_MAVEN },
pypi: { pypi: ORIGINS_PYPI },
Expand Down Expand Up @@ -188,6 +192,14 @@ export function getRubyGemsRevisions(token, path) {
return get(url(`${ORIGINS_RUBYGEMS}/${path}/revisions`), token)
}

export function getCondaSearch(token, path) {
return get(url(`${ORIGINS_CONDA}/${path}`), token)
}

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

export function getCrateSearch(token, path) {
return get(url(`${ORIGINS_CRATE}/${path}`), token)
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/DefinitionEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import git from '../images/Git-Logo-2Color.png'
import npm from '../images/n-large.png'
import pypi from '../images/pypi.png'
import gem from '../images/gem.png'
import conda from '../images/conda.svg'
import cargo from '../images/cargo.png'
import nuget from '../images/nuget.svg'
import debian from '../images/debian.png'
Expand Down Expand Up @@ -49,7 +50,7 @@ class DefinitionEntry extends React.Component {
static defaultProps = {}

isSourceComponent(component) {
return ['github', 'sourcearchive', 'debsrc'].includes(component.provider)
return ['github', 'sourcearchive', 'debsrc', 'condasrc'].includes(component.provider)
}

fieldChange(field, equality = isEqual, transform = a => a) {
Expand Down Expand Up @@ -453,6 +454,7 @@ class DefinitionEntry extends React.Component {
getImage(definition) {
if (definition.coordinates.type === 'git') return git
if (definition.coordinates.type === 'npm') return npm
if (definition.coordinates.type === 'conda') return conda
if (definition.coordinates.type === 'crate') return cargo
if (definition.coordinates.type === 'pypi') return pypi
if (definition.coordinates.type === 'gem') return gem
Expand Down
2 changes: 2 additions & 0 deletions src/components/FilterBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import maven from '../images/maven.png'
import nuget from '../images/nuget.png'
import pod from '../images/pod.png'
import git from '../images/Git-Logo-2Color.png'
import conda from '../images/conda.svg'
import crate from '../images/cargo.png'
import gem from '../images/gem.png'
import pypi from '../images/pypi.png'
Expand All @@ -23,6 +24,7 @@ const types = {
maven: maven,
nuget: nuget,
git: git,
conda: conda,
crate: crate,
pod: pod,
deb: debian,
Expand Down
28 changes: 28 additions & 0 deletions src/components/HarvestQueueList.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
NpmVersionPicker,
MavenVersionPicker,
PyPiVersionPicker,
CondaVersionPicker,
CrateVersionPicker,
DebianVersionPicker,
NuGetVersionPicker,
Expand All @@ -25,6 +26,9 @@ import npm from '../images/n-large.png'
import pypi from '../images/pypi.png'
import debian from '../images/debian.png'
import gem from '../images/gem.png'
import anaconda_main from '../images/anaconda-main.svg'
import anaconda_r from '../images/anaconda-r.png'
import conda_forge from '../images/conda-forge.png'
import cargo from '../images/cargo.png'
import maven from '../images/maven.png'
import nuget from '../images/nuget.png'
Expand Down Expand Up @@ -73,6 +77,15 @@ class HarvestQueueList extends React.Component {
this.props.onChange(request, newRequest)
}

condaVersionChanged(request, value) {
const newRequest = clone(request)
let [namespace, revision] = value.split(':')
newRequest.namespace = namespace
newRequest.revision = revision
this.setState({ contentSeq: this.state.contentSeq + 1 })
this.props.onChange(request, newRequest)
}

renderButtons(request) {
return (
<div className="list-activity-area">
Expand All @@ -84,6 +97,15 @@ class HarvestQueueList extends React.Component {
onChange={this.commitChanged.bind(this, request)}
/>
)}
{request.provider === 'anaconda-main' && (
<CondaVersionPicker request={request} onChange={this.condaVersionChanged.bind(this, request)} />
)}
{request.provider === 'anaconda-r' && (
<CondaVersionPicker request={request} onChange={this.condaVersionChanged.bind(this, request)} />
)}
{request.provider === 'cratesio' && (
Copy link
Collaborator

@qtomlinson qtomlinson Jul 30, 2024

Choose a reason for hiding this comment

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

This is a duplicate of line 124

<CrateVersionPicker request={request} onChange={this.versionChanged.bind(this, request)} />
)}
{request.provider === 'npmjs' && (
<NpmVersionPicker request={request} onChange={this.versionChanged.bind(this, request)} />
)}
Expand All @@ -96,6 +118,9 @@ class HarvestQueueList extends React.Component {
{request.provider === 'rubygems' && (
<RubyGemsVersionPicker request={request} onChange={this.versionChanged.bind(this, request)} />
)}
{request.provider === 'conda-forge' && (
<CondaVersionPicker request={request} onChange={this.condaVersionChanged.bind(this, request)} />
)}
{request.provider === 'cratesio' && (
<CrateVersionPicker request={request} onChange={this.versionChanged.bind(this, request)} />
)}
Expand Down Expand Up @@ -150,6 +175,9 @@ class HarvestQueueList extends React.Component {
if (request.provider === 'npmjs') return npm
if (request.provider === 'pypi') return pypi
if (request.provider === 'rubygems') return gem
if (request.provider === 'anaconda-main') return anaconda_main
if (request.provider === 'anaconda-r') return anaconda_r
if (request.provider === 'conda-forge') return conda_forge
if (request.provider === 'cratesio') return cargo
if (request.provider === 'mavencentral') return maven
if (request.provider === 'nuget') return nuget
Expand Down
4 changes: 4 additions & 0 deletions src/components/Navigation/Pages/PageHarvest/PageHarvest.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
NpmSelector,
MavenSelector,
NuGetSelector,
CondaSelector,
CrateSelector,
DebianSelector,
ComposerSelector,
Expand Down Expand Up @@ -114,11 +115,14 @@ class PageHarvest extends Component {
<Row className="show-grid">
<Col md={6}>{this.renderProviderButtons()}</Col>
<Col md={4}>
{activeProvider.value === 'anaconda-main' && <CondaSelector provider='anaconda-main' onChange={this.onAddRequest} />}
{activeProvider.value === 'anaconda-r' && <CondaSelector provider='anaconda-r' onChange={this.onAddRequest} />}
{activeProvider.value === 'github' && <GitHubSelector onChange={this.onAddRequest} />}
{activeProvider.value === 'mavencentral' && <MavenSelector onChange={this.onAddRequest} />}
{activeProvider.value === 'npmjs' && <NpmSelector onChange={this.onAddRequest} />}
{activeProvider.value === 'nuget' && <NuGetSelector onChange={this.onAddRequest} />}
{activeProvider.value === 'cratesio' && <CrateSelector onChange={this.onAddRequest} />}
{activeProvider.value === 'conda-forge' && <CondaSelector provider='conda-forge' onChange={this.onAddRequest} />}
{activeProvider.value === 'packagist' && <ComposerSelector onChange={this.onAddRequest} />}
{activeProvider.value === 'pypi' && <PyPiSelector onChange={this.onAddRequest} />}
{activeProvider.value === 'rubygems' && <RubyGemsSelector onChange={this.onAddRequest} />}
Expand Down
3 changes: 3 additions & 0 deletions src/components/Navigation/Pages/PageStats/PageStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import maven from '../../../../images/maven.png'
import nuget from '../../../../images/nuget.png'
import pod from '../../../../images/pod.png'
import git from '../../../../images/Git-Logo-2Color.png'
import conda from '../../../../images/conda.svg'
import crate from '../../../../images/cargo.png'
import composer from '../../../../images/packagist.png'
import gem from '../../../../images/gem.png'
Expand All @@ -34,6 +35,8 @@ const types = {
maven: maven,
nuget: nuget,
git: git,
conda: conda,
condasrc: conda,
crate: crate,
deb: debian,
debsrc: debian,
Expand Down
4 changes: 2 additions & 2 deletions src/components/Navigation/Ui/ComponentButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class ComponentButtons extends Component {
}

isSourceComponent(component) {
return ['github', 'sourcearchive', 'debsrc'].includes(component.provider)
return ['github', 'sourcearchive', 'debsrc', 'condasrc'].includes(component.provider)
}

_isProviderSupported(component) {
Expand Down Expand Up @@ -152,7 +152,7 @@ class ComponentButtons extends Component {
)}
{!isDefinitionEmpty && onInspect && (
<ButtonWithTooltip tip="Dig into this definition">
<Button className="list-fa-button" onClick={this.inspectComponent.bind(this, currentComponent, definition)}>
<Button className="list-fa-button" onClick={this.inspectComponent.bind(this, currentComponent, definition)}>
<i className="fas fa-search" />
</Button>
</ButtonWithTooltip>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Navigation/Ui/ComponentDetailsButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ComponentDetailsButtons extends Component {
}

isSourceComponent(component) {
return ['github', 'sourcearchive', 'debsrc'].includes(component.provider)
return ['github', 'sourcearchive', 'debsrc', 'condasrc'].includes(component.provider)
}

openSourceForComponent = definition => {
Expand Down
65 changes: 65 additions & 0 deletions src/components/Providers/Selectors/CondaSelector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation and others. Licensed under the MIT license.
// SPDX-License-Identifier: MIT

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

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

constructor(props) {
super(props)
this.state = { isLoading: false, options: [], focus: false }
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: 'conda', provider: this.props.provider, name: value.id }, 'package')
}

async getOptions(value) {
try {
this.setState({ ...this.state, isLoading: true })
const options = await getCondaSearch(this.props.token, `${this.props.provider}/${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="conda-selector"
className="harvest-search"
useCache={false}
options={options}
placeholder={'Pick a Conda Package 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>
)
}
}
75 changes: 75 additions & 0 deletions src/components/Providers/VersionPickers/CondaVersionPicker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation and others. Licensed under the MIT license.
// SPDX-License-Identifier: MIT

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { getCondaRevisions } from '../../../api/clearlyDefined'
import Autocomplete from '../../Navigation/Ui/Autocomplete'

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

constructor(props) {
super(props)
this.state = { customValues: [], options: [] }
this.onChange = this.onChange.bind(this)
this.filter = this.filter.bind(this)
}

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

async getOptions(value) {
try {
const { name, namespace, provider } = this.props.request
const options = await getCondaRevisions(this.props.token, `${provider}/${namespace || '-'}/${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 } = this.state
const list = customValues.concat(options)
return (
<Autocomplete
id="conda-version-picker"
options={list}
defaultInputValue={defaultInputValue}
placeholder={options.length === 0 ? 'Could not fetch versions, type a Conda version' : 'Pick a Conda version'}
onChange={this.onChange}
bodyContainer
clearButton
allowNew
newSelectionPrefix="Version:"
emptyLabel=""
filterBy={this.filter}
selectHintOnEnter
/>
)
}
}
Loading