-
Notifications
You must be signed in to change notification settings - Fork 30
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
Package Graph #102
Package Graph #102
Changes from 3 commits
170aafc
f3e8446
a559c89
86a1d28
97ded1c
c99b81a
7915432
bdfca10
e25fe8c
516fc11
2d497fb
3e786e0
b0c10dd
906810a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -524,6 +524,29 @@ async def env_packages(self, env: str) -> Dict[str, List[str]]: | |
|
||
return {"packages": [normalize_pkg_info(package) for package in data]} | ||
|
||
async def pkg_depends(self, pkg: str) -> Dict[str, List[str]]: | ||
"""List environment packages dependencies. | ||
|
||
Args: | ||
pkg (str): Package name | ||
|
||
Returns: | ||
{"package": List[dependencies]} | ||
""" | ||
resp = {} | ||
ans = await self._execute(self.manager, "repoquery", "depends", "--json", pkg) | ||
_, output = ans | ||
query = self._clean_conda_json(output) | ||
|
||
if "error" not in query: | ||
for dep in query['result']['pkgs'] : | ||
if type(dep) is dict : | ||
deps = dep.get('depends', None) | ||
if deps : | ||
resp[dep['name']] = deps | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would returned an empty list rather than skipping the entry. It seems strange to have:
if
If |
||
|
||
return resp | ||
|
||
async def list_available(self) -> Dict[str, List[Dict[str, str]]]: | ||
"""List all available packages | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -349,12 +349,17 @@ async def get(self): | |
"""`GET /packages` Search for packages. | ||
|
||
Query arguments: | ||
package (str): optional package name to seach dependencies | ||
query (str): optional string query | ||
""" | ||
pkg = self.get_query_argument("package", "") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather use a query arguments like |
||
query = self.get_query_argument("query", "") | ||
|
||
idx = None | ||
if query: # Specific search | ||
if pkg : | ||
idx = self._stack.put(self.env_manager.pkg_depends, pkg) | ||
|
||
elif query: # Specific search | ||
idx = self._stack.put(self.env_manager.package_search, query) | ||
|
||
else: # List all available | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -50,7 +50,11 @@ | |||
"@lumino/coreutils": "^1.3.0", | ||||
"@lumino/signaling": "^1.2.0", | ||||
"@lumino/widgets": "^1.6.0", | ||||
"@types/react-d3-graph": "^2.3.4", | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a |
||||
"d3": "^5.5.0", | ||||
"jupyterlab_toastify": "^4.1.2", | ||||
"react": "^16.4.1", | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Unneeded as set as |
||||
"react-d3-graph": "^2.5.0", | ||||
"react-virtualized": "^9.21.1", | ||||
"semver": "^6.0.0||^7.0.0", | ||||
"typestyle": "^2.0.0" | ||||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -38,6 +38,10 @@ export interface IPkgListProps { | |||||||
* Package item version selection handler | ||||||||
*/ | ||||||||
onPkgChange: (pkg: Conda.IPackage, version: string) => void; | ||||||||
/** | ||||||||
* Package item graph dependencies handler | ||||||||
*/ | ||||||||
onPkgGraph: (pkg: Conda.IPackage) => void; | ||||||||
} | ||||||||
|
||||||||
/** | ||||||||
|
@@ -143,6 +147,23 @@ export class CondaPkgList extends React.Component<IPkgListProps> { | |||||||
return <span>{rowData.name}</span>; | ||||||||
}; | ||||||||
|
||||||||
const versionRender = ({ rowData }: ICellRender): JSX.Element => ( | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happen for not installed packages? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing. I think we should open this graph from another place, not from the version of the package but I'm not sure where to add it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think of this mock-up? The idea is to add an icon in the toolbar above the packages list (it will be absent if mamba is not available - or disabled with a title advertising mamba 😉 ). Then selecting a package will result in the display of its dependencies graph. If not package selected, a help message such Select a package to display its dependency graph. This means also to change the current behavior as onClick on a row changes the status of the package. This should then be restricted to the status icon probably. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good for me! |
||||||||
<a | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we handle directly here the fact that mamba will be able to render the graph but not conda? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you want to do it? I'm not following you, sorry. |
||||||||
className={ | ||||||||
rowData.updatable | ||||||||
? classes(Style.Updatable, Style.Link) | ||||||||
: Style.Link | ||||||||
} | ||||||||
href="#" | ||||||||
onClick={(evt): void => { | ||||||||
this.props.onPkgGraph(rowData); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to stop the propagation of the event otherwise it will trigger an update or a remove action on the package.
Suggested change
|
||||||||
}} | ||||||||
rel="noopener noreferrer" | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
It would be nice to add a title to inform the user of the action resulting of the click. |
||||||||
> | ||||||||
{rowData.version_installed} | ||||||||
</a> | ||||||||
); | ||||||||
|
||||||||
const changeRender = ({ rowData }: ICellRender): JSX.Element => ( | ||||||||
<div className={'lm-Widget'}> | ||||||||
<HTMLSelect | ||||||||
|
@@ -235,17 +256,7 @@ export class CondaPkgList extends React.Component<IPkgListProps> { | |||||||
/> | ||||||||
)} | ||||||||
<Column | ||||||||
cellRenderer={({ rowData }: ICellRender): JSX.Element => ( | ||||||||
<span | ||||||||
className={ | ||||||||
rowData.updatable | ||||||||
? classes(Style.Updatable, Style.Cell) | ||||||||
: Style.Cell | ||||||||
} | ||||||||
> | ||||||||
{rowData.version_installed} | ||||||||
</span> | ||||||||
)} | ||||||||
cellRenderer={versionRender} | ||||||||
dataKey="version_installed" | ||||||||
disableSort | ||||||||
label="Version" | ||||||||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,87 @@ | ||||||||
import { | ||||||||
ReactWidget | ||||||||
} from '@jupyterlab/apputils'; | ||||||||
|
||||||||
import { Graph, GraphData, GraphNode, GraphLink } from "react-d3-graph"; | ||||||||
|
||||||||
import * as React from 'react'; | ||||||||
|
||||||||
import { Conda } from '../tokens'; | ||||||||
|
||||||||
export class PkgGraph extends ReactWidget { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you refractor this into a thin The React component would have:
Call
The idea is to have a component that will be easily extendable to display the dependency graph of an environment. |
||||||||
constructor(pkgManager: Conda.IPackageManager, pkg: string) { | ||||||||
super(); | ||||||||
this._package = pkg; | ||||||||
this._pkgManager = pkgManager; | ||||||||
this._loading = true; | ||||||||
|
||||||||
// create a network | ||||||||
this._data = { nodes: [], links: [] }; | ||||||||
if (this._package) this._updatePackages(); | ||||||||
} | ||||||||
|
||||||||
public showGraph(pkg: string): void { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused? |
||||||||
this._package = pkg; | ||||||||
this._updatePackages(); | ||||||||
} | ||||||||
|
||||||||
private async _updatePackages(): Promise<void> { | ||||||||
this._loading = true; | ||||||||
this.update(); | ||||||||
|
||||||||
this._pkgManager.getDependencies(this._package, true) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add catch to at least display error in the web console. But better is also to provide a human readable error message. |
||||||||
.then( available => { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The code uses as much as possible |
||||||||
this._data.nodes = []; | ||||||||
this._data.links = []; | ||||||||
|
||||||||
Object.keys(available).forEach( key => { | ||||||||
this._data.nodes.push({ id: key }); | ||||||||
available[key].forEach( dep => { | ||||||||
const dependencie = dep.split(' ')[0]; | ||||||||
if (!this._data.nodes.find(value => value.id === dependencie)) { | ||||||||
this._data.nodes.push({ id: dependencie }); | ||||||||
} | ||||||||
this._data.links.push({ source: key, target: dependencie }); | ||||||||
}); | ||||||||
}); | ||||||||
|
||||||||
this._loading = false; | ||||||||
this.update(); | ||||||||
}) | ||||||||
} | ||||||||
|
||||||||
render(): JSX.Element { | ||||||||
const config = { | ||||||||
nodeHighlightBehavior: true, | ||||||||
node: { | ||||||||
color: "lightblue", | ||||||||
highlightStrokeColor: "blue", | ||||||||
}, | ||||||||
link: { highlightColor: "blue" }, | ||||||||
}; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you extract this at the class level or even better as default value but tunable through an constructor argument? Do we need JLab theme here? |
||||||||
return ( | ||||||||
<div> | ||||||||
{ | ||||||||
this._loading | ||||||||
? | ||||||||
<span>Loading dependencies</span> | ||||||||
: | ||||||||
<div> | ||||||||
{ | ||||||||
this._data.nodes.length !== 0 | ||||||||
? | ||||||||
<Graph id="graph-id" data={this._data} config={config}/> | ||||||||
: | ||||||||
<span>This is a pip package</span> | ||||||||
} | ||||||||
</div> | ||||||||
} | ||||||||
</div> | ||||||||
); | ||||||||
} | ||||||||
|
||||||||
private _pkgManager: Conda.IPackageManager; | ||||||||
private _data: GraphData<GraphNode, GraphLink>; | ||||||||
private _package: string; | ||||||||
private _loading: boolean; | ||||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -721,6 +721,34 @@ export class CondaPackage implements Conda.IPackageManager { | |||||
} | ||||||
} | ||||||
|
||||||
async getDependencies( | ||||||
pkg: string, | ||||||
cancellable = true | ||||||
): Promise<Conda.IPackageDeps> { | ||||||
this._cancelTasks(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are introducing a second cancellable action. So it would be good to change a bit the API to store in the cancellable stack objects Meaning here:
Suggested change
|
||||||
const request: RequestInit = { | ||||||
method: 'GET' | ||||||
}; | ||||||
|
||||||
const { promise, cancel } = Private.requestServer( | ||||||
URLExt.join('conda', 'packages') + | ||||||
URLExt.objectToQueryString({ package: pkg }), | ||||||
request | ||||||
); | ||||||
|
||||||
let idx: number; | ||||||
if (cancellable) { | ||||||
idx = this._cancellableStack.push(cancel) - 1; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Follow-up on cancellation changes:
Suggested change
|
||||||
} | ||||||
|
||||||
const response = await promise; | ||||||
if (idx) { | ||||||
this._cancellableStack.splice(idx, 1); | ||||||
} | ||||||
const data = (await response.json()) as Conda.IPackageDeps; | ||||||
return Promise.resolve(data); | ||||||
} | ||||||
|
||||||
async refreshAvailablePackages(cancellable = true): Promise<void> { | ||||||
await this._getAvailablePackages(true, cancellable); | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -192,6 +192,16 @@ export namespace Conda { | |||||
* @param environment Environment name | ||||||
*/ | ||||||
remove(packages: Array<string>, environment?: string): Promise<void>; | ||||||
/** | ||||||
* Get packages dependencies list. | ||||||
* | ||||||
* @param environment Environment name | ||||||
fcollonval marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
* @param package Package name | ||||||
* @param cancellable Can this asynchronous action be cancelled? | ||||||
* | ||||||
* @returns The package list | ||||||
*/ | ||||||
getDependencies(pkg: string, cancellable: boolean): Promise<{[key: string]: [string]}> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
/** | ||||||
* Signal emitted when some package actions are executed. | ||||||
*/ | ||||||
|
@@ -233,6 +243,9 @@ export namespace Conda { | |||||
version_selected?: string; | ||||||
updatable?: boolean; | ||||||
} | ||||||
export interface IPackageDeps { | ||||||
[key: string]: [string] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
export interface IPackageChange { | ||||||
/** | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As conda does not support this, the manager should be tested. And in this particular case a default value like {name:
null
} to signify it can't be done is a good candidate.