diff --git a/jbrowse_config.json b/jbrowse_config.json index 2f917bb..7dc69a7 100644 --- a/jbrowse_config.json +++ b/jbrowse_config.json @@ -1,4 +1,197 @@ { + "assemblies": [ + { + "name": "volvox", + "aliases": [ + "vvx" + ], + "sequence": { + "type": "ReferenceSequenceTrack", + "trackId": "volvox_refseq", + "metadata": { + "date": "2020-08-20" + }, + "formatAbout": { + "hideUris": true, + "config": "jexl:{extraField:'important data'}" + }, + "adapter": { + "type": "TwoBitAdapter", + "twoBitLocation": { + "uri": "test_data/volvox/volvox.2bit", + "locationType": "UriLocation" + } + } + }, + "refNameAliases": { + "adapter": { + "type": "FromConfigAdapter", + "adapterId": "W6DyPGJ0UU", + "features": [ + { + "refName": "ctgA", + "uniqueId": "alias1", + "aliases": [ + "A", + "contigA" + ] + }, + { + "refName": "ctgB", + "uniqueId": "alias2", + "aliases": [ + "B", + "contigB" + ] + } + ] + } + } + }, + { + "name": "volvox_tripalauth", + "sequence": { + "type": "ReferenceSequenceTrack", + "trackId": "volvox_tripalauth-ReferenceSequenceTrack", + "adapter": { + "type": "TwoBitAdapter", + "twoBitLocation": { + "uri": "test_data/volvox/volvox.2bit", + "locationType": "UriLocation", + "internetAccountId": "customTripalAuth" + } + } + } + } + ], + "connections": [], + "defaultSession": { + "name": "Integration test example", + "views": [ + { + "id": "integration_test", + "type": "LinearGenomeView", + "offsetPx": 2000, + "bpPerPx": 0.05, + "displayedRegions": [ + { + "refName": "ctgA", + "start": 0, + "end": 50001, + "assemblyName": "volvox" + } + ] + } + ], + "widgets": { + "hierarchicalTrackSelector": { + "id": "hierarchicalTrackSelector", + "type": "HierarchicalTrackSelectorWidget", + "filterText": "", + "view": "integration_test" + } + }, + "activeWidgets": { + "hierarchicalTrackSelector": "hierarchicalTrackSelector" + } + }, + "tracks": [ + { + "type": "QuantitativeTrack", + "trackId": "tripalauth_bigwig", + "name": "TripalAuth BigWig", + "category": [ + "Authentication" + ], + "assemblyNames": [ + "volvox" + ], + "adapter": { + "type": "BigWigAdapter", + "bigWigLocation": { + "locationType": "UriLocation", + "uri": "test_data/volvox/volvox.bw", + "internetAccountId": "ActivatingCrops" + } + } + }, + { + "type": "VariantTrack", + "trackId": "tripalauth_vcf", + "name": "TripalAuth VCF", + "assemblyNames": [ + "volvox" + ], + "category": [ + "Authentication" + ], + "adapter": { + "type": "VcfTabixAdapter", + "vcfGzLocation": { + "uri": "test_data/volvox/volvox.filtered.vcf.gz", + "locationType": "UriLocation", + "internetAccountId": "ActivatingCrops" + }, + "index": { + "location": { + "uri": "test_data/volvox/volvox.filtered.vcf.gz.tbi", + "locationType": "UriLocation", + "internetAccountId": "ActivatingCrops" + } + } + } + }, + { + "type": "QuantitativeTrack", + "trackId": "noauth_bigwig", + "name": "NoAuth BigWig", + "category": [ + "Authentication" + ], + "assemblyNames": [ + "volvox" + ], + "adapter": { + "type": "BigWigAdapter", + "bigWigLocation": { + "locationType": "UriLocation", + "uri": "test_data/volvox/volvox.bw" + } + } + }, + { + "type": "VariantTrack", + "trackId": "noauth_vcf", + "name": "NoAuth VCF", + "assemblyNames": [ + "volvox" + ], + "category": [ + "Authentication" + ], + "adapter": { + "type": "VcfTabixAdapter", + "vcfGzLocation": { + "uri": "test_data/volvox/volvox.filtered.vcf.gz", + "locationType": "UriLocation" + }, + "index": { + "location": { + "uri": "test_data/volvox/volvox.filtered.vcf.gz.tbi", + "locationType": "UriLocation" + } + } + } + } + ], + "internetAccounts": [ + { + "type": "DrupalRestAuthModel", + "internetAccountId": "ActivatingCrops", + "name": "Activating Crops Tripal Authentication", + "description": "Requires login to the Activating Crops website" + } + ], "plugins": [ { "name": "DrupalRestAuthModel", diff --git a/src/DrupalRestAuthModel/DrupalRestAuthLoginForm.tsx b/src/DrupalRestAuthModel/DrupalRestAuthLoginForm.tsx new file mode 100644 index 0000000..c5bc571 --- /dev/null +++ b/src/DrupalRestAuthModel/DrupalRestAuthLoginForm.tsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react' +import { Button, DialogContent, DialogActions, TextField } from '@mui/material' +import { Dialog } from '@jbrowse/core/ui' + +export function DrupalRestAuthLoginForm({ + internetAccountId, + handleClose, +}: { + internetAccountId: string + handleClose: (arg?: string) => void +}) { + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + + return ( + +
{ + if (username && password) { + handleClose(btoa(`${username}:${password}`)) + } else { + handleClose() + } + event.preventDefault() + }} + > + + setUsername(event.target.value)} + margin="dense" + /> + setPassword(event.target.value)} + margin="dense" + /> + + + + + +
+
+ ) +} diff --git a/src/DrupalRestAuthModel/configSchema.ts b/src/DrupalRestAuthModel/configSchema.ts new file mode 100644 index 0000000..c4b0619 --- /dev/null +++ b/src/DrupalRestAuthModel/configSchema.ts @@ -0,0 +1,43 @@ +import { ConfigurationSchema } from '@jbrowse/core/configuration' +import { Instance } from 'mobx-state-tree' +import { BaseInternetAccountConfig } from '@jbrowse/core/pluggableElementTypes/models' + +/** + * #config DrupalRestAuthModel + */ +function x() { } // eslint-disable-line @typescript-eslint/no-unused-vars + +const DrupalRestConfigSchema = ConfigurationSchema( + 'DrupalRestAuthInternetAccount', + { + /** + * #slot + */ + tokenType: { + description: 'a custom name for a token to include in the header', + type: 'string', + defaultValue: 'Basic', + }, + /** + * #slot + */ + validateWithHEAD: { + description: 'validate the token with a HEAD request before using it', + type: 'boolean', + defaultValue: true, + }, + }, + { + /** + * #baseConfiguration + */ + baseConfiguration: BaseInternetAccountConfig, + explicitlyTyped: true, + }, +) + +export type DrupalRestAuthInternetAccountConfigModel = typeof DrupalRestConfigSchema + +export type DrupalRestAuthInternetAccountConfig = + Instance +export default DrupalRestConfigSchema diff --git a/src/DrupalRestAuthModel/model.tsx b/src/DrupalRestAuthModel/model.tsx new file mode 100644 index 0000000..947f231 --- /dev/null +++ b/src/DrupalRestAuthModel/model.tsx @@ -0,0 +1,112 @@ +import { ConfigurationReference, getConf } from '@jbrowse/core/configuration' +import { InternetAccount } from '@jbrowse/core/pluggableElementTypes/models' +import { UriLocation } from '@jbrowse/core/util/types' +import { Instance, types, getRoot } from 'mobx-state-tree' + +// locals +import { DrupalRestAuthInternetAccountConfigModel } from './configSchema' +import { DrupalRestAuthLoginForm } from './DrupalRestAuthLoginForm' +// import { getResponseError } from '../util' +async function getError(response: Response) { + try { + return response.text() + } catch (e) { + return response.statusText + } +} + +async function getResponseError({ + response, + reason, + statusText, +}: { + response: Response + reason?: string + statusText?: string +}) { + return [ + `HTTP ${response.status}`, + reason, + statusText ?? (await getError(response)), + ] + .filter(f => !!f) + .join(' - ') +} + + +/** + * #stateModel DrupalRestAuthInternetAccount + */ +const stateModelFactory = ( + configSchema: DrupalRestAuthInternetAccountConfigModel, +) => { + return InternetAccount.named('DrupalRestAuthInternetAccount') + .props({ + /** + * #property + */ + type: types.literal('DrupalRestAuthInternetAccount'), + /** + * #property + */ + configuration: ConfigurationReference(configSchema), + }) + .views(self => ({ + /** + * #getter + */ + get validateWithHEAD(): boolean { + return getConf(self, 'validateWithHEAD') + }, + })) + .actions(self => ({ + /** + * #action + */ + getTokenFromUser( + resolve: (token: string) => void, + reject: (error: Error) => void, + ) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { session } = getRoot(self) + session.queueDialog((doneCallback: () => void) => [ + DrupalRestAuthLoginForm, + { + internetAccountId: self.internetAccountId, + handleClose: (token: string) => { + if (token) { + resolve(token) + } else { + reject(new Error('User cancelled entry')) + } + doneCallback() + }, + }, + ]) + }, + /** + * #action + */ + async validateToken(token: string, location: UriLocation) { + console.log('here!!!:)') + if (!self.validateWithHEAD) { + return token + } + const newInit = self.addAuthHeaderToInit({ method: 'HEAD' }, token) + const response = await fetch(location.uri, newInit) + if (!response.ok) { + throw new Error( + await getResponseError({ + response, + reason: 'Error validating token', + }), + ) + } + return token + }, + })) +} + +export default stateModelFactory +export type DrupalRestAuthStateModel = ReturnType +export type DrupalRestAuthModel = Instance diff --git a/src/HelloView/components/HelloView.test.tsx b/src/HelloView/components/HelloView.test.tsx deleted file mode 100644 index be64509..0000000 --- a/src/HelloView/components/HelloView.test.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import { render, fireEvent, screen } from '@testing-library/react' -import '@testing-library/jest-dom' -import HelloView from './HelloView' - -it('renders and reacts to button push', async () => { - render() - - expect(screen.getByRole('heading')).toHaveTextContent( - 'Hello plugin developers!', - ) - - fireEvent.click(screen.getByText('Push the button')) - await screen.findByText('Whoa! You pushed the button!') -}) diff --git a/src/HelloView/components/HelloView.tsx b/src/HelloView/components/HelloView.tsx deleted file mode 100644 index 6cec198..0000000 --- a/src/HelloView/components/HelloView.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { useState } from 'react' - -export default function ReactComponent() { - const [pushed, setPushed] = useState('') - return ( -
-

Hello plugin developers!

- -

{pushed}

-
- ) -} diff --git a/src/HelloView/index.ts b/src/HelloView/index.ts deleted file mode 100644 index 4dbfdab..0000000 --- a/src/HelloView/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as ReactComponent } from './components/HelloView' -export { default as stateModel } from './stateModel' diff --git a/src/HelloView/stateModel.ts b/src/HelloView/stateModel.ts deleted file mode 100644 index 14669fa..0000000 --- a/src/HelloView/stateModel.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ElementId } from '@jbrowse/core/util/types/mst' -import { types } from 'mobx-state-tree' - -const stateModel = types - .model({ - id: ElementId, - type: types.literal('HelloView'), - }) - .actions(() => ({ - // unused but required by your view - setWidth() {}, - })) - -export default stateModel diff --git a/src/index.ts b/src/index.ts index e7d682a..ef3ecbd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,21 +3,21 @@ import PluginManager from '@jbrowse/core/PluginManager' import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType' import { AbstractSessionModel, isAbstractMenuManager } from '@jbrowse/core/util' import { version } from '../package.json' -import { - ReactComponent as HelloViewReactComponent, - stateModel as helloViewStateModel, -} from './HelloView' +import { InternetAccountType } from '@jbrowse/core/pluggableElementTypes' +import configSchema from './DrupalRestAuthModel/configSchema' +import modelFactory from './DrupalRestAuthModel/model' export default class DrupalRestAuthModelPlugin extends Plugin { name = 'DrupalRestAuthModelPlugin' version = version install(pluginManager: PluginManager) { - pluginManager.addViewType(() => { - return new ViewType({ - name: 'HelloView', - stateModel: helloViewStateModel, - ReactComponent: HelloViewReactComponent, + console.log('here') + pluginManager.addInternetAccountType(() => { + return new InternetAccountType({ + name: 'DrupalRestAuthInternetAccount', + configSchema, + stateModel: modelFactory(configSchema), }) }) }