Skip to content
This repository has been archived by the owner on Aug 19, 2020. It is now read-only.

Commit

Permalink
Update org viewer (#24)
Browse files Browse the repository at this point in the history
Update the org viewer demo to use the updated API.
  • Loading branch information
bpierre committed May 27, 2020
1 parent 45f092e commit 2540494
Show file tree
Hide file tree
Showing 19 changed files with 547 additions and 105 deletions.
3 changes: 0 additions & 3 deletions packages/connector-thegraph/package.json
Expand Up @@ -20,12 +20,9 @@
"typescript": "^3.8.3"
},
"dependencies": {
"@types/node-fetch": "^2.5.7",
"@urql/core": "^1.11.7",
"graphql": "^15.0.0",
"graphql-tag": "^2.10.3",
"isomorphic-unfetch": "^3.0.0",
"node-fetch": "^2.6.0",
"plumbery-core": "*",
"wonka": "^4.0.9"
}
Expand Down
43 changes: 26 additions & 17 deletions packages/connector-thegraph/src/connector.ts
@@ -1,12 +1,3 @@
import * as queries from './queries'
import GraphQLWrapper from './core/GraphQLWrapper'
import {
parseApp,
parseApps,
parsePermissions,
parseRepo,
parseRoles,
} from './parsers'
import {
ConnectorInterface,
Permission,
Expand All @@ -17,21 +8,39 @@ import {
Role,
RoleData,
} from 'plumbery-core'
import * as queries from './queries'
import GraphQLWrapper from './core/GraphQLWrapper'
import {
parseApp,
parseApps,
parsePermissions,
parseRepo,
parseRoles,
} from './parsers'

export type ConnectorTheGraphConfig = {
daoSubgraphUrl: string
daoSubgraphUrl?: string
verbose?: boolean
}

const DAO_SUBGRAPH_URL_DEFAULT =
'https://api.thegraph.com/subgraphs/name/aragon/aragon-mainnet'

// https://api.thegraph.com/subgraphs/name/ensdomains/ens
// https://api.thegraph.com/subgraphs/name/ensdomains/ensrinkeby

export default class ConnectorTheGraph extends GraphQLWrapper
implements ConnectorInterface {
constructor(config: ConnectorTheGraphConfig) {
super(config.daoSubgraphUrl, config.verbose)
constructor({
daoSubgraphUrl = DAO_SUBGRAPH_URL_DEFAULT,
verbose = false,
}: ConnectorTheGraphConfig = {}) {
super(daoSubgraphUrl, verbose)
}

async rolesForAddress(appAddress: string): Promise<Role[]> {
const result = await this.performQuery(queries.ROLE_BY_APP_ADDRESS, {
appAddress,
appAddress: appAddress.toLowerCase(),
})

const datas = this.parseQueryResult(parseRoles, result)
Expand All @@ -43,7 +52,7 @@ export default class ConnectorTheGraph extends GraphQLWrapper

async permissionsForOrg(orgAddress: string): Promise<Permission[]> {
const result = await this.performQuery(queries.ORGANIZATION_PERMISSIONS, {
orgAddress,
orgAddress: orgAddress.toLowerCase(),
})

const datas = this.parseQueryResult(parsePermissions, result)
Expand All @@ -55,7 +64,7 @@ export default class ConnectorTheGraph extends GraphQLWrapper

async appsForOrg(orgAddress: string): Promise<App[]> {
const result = await this.performQuery(queries.ORGANIZATION_APPS, {
orgAddress,
orgAddress: orgAddress.toLowerCase(),
})

const datas = this.parseQueryResult(parseApps, result)
Expand All @@ -67,7 +76,7 @@ export default class ConnectorTheGraph extends GraphQLWrapper

async appByAddress(appAddress: string): Promise<App> {
const result = await this.performQuery(queries.APP_BY_ADDRESS, {
appAddress,
appAddress: appAddress.toLowerCase(),
})

const data = this.parseQueryResult(parseApp, result)
Expand All @@ -77,7 +86,7 @@ export default class ConnectorTheGraph extends GraphQLWrapper

async repoForApp(appAddress: string): Promise<Repo> {
const result = await this.performQuery(queries.REPO_BY_APP_ADDRESS, {
appAddress,
appAddress: appAddress.toLowerCase(),
})

const data = this.parseQueryResult(parseRepo, result)
Expand Down
3 changes: 1 addition & 2 deletions packages/connector-thegraph/src/core/GraphQLWrapper.ts
@@ -1,4 +1,3 @@
import 'isomorphic-unfetch';
import { Client } from '@urql/core'
import { DocumentNode } from 'graphql';
import {
Expand Down Expand Up @@ -52,4 +51,4 @@ export default class GraphQLWrapper {

return `\nSubgraph: ${subgraphUrl}\nArguments: ${argsStr}\nQuery: ${queryStr}Returned data: ${dataStr}`
}
}
}
Expand Up @@ -4,18 +4,40 @@ import ConnectorEthereum, {
import ConnectorTheGraph, {
ConnectorTheGraphConfig,
} from 'plumbery-connector-thegraph'
import ConnectorJson, { ConnectorJsonConfig } from './ConnectorJson'
import Organization from '../entities/Organization'
import { ConnectorInterface } from './ConnectorInterface'
import ConnectorJson, { ConnectorJsonConfig } from './ConnectorJson'

type ConnectOptions = {
ipfs?: ResolveIpfs
}
type ConnectorDeclaration =
| ConnectorInterface
| [string, object | undefined]
| string

type ConnectorDeclaration = ConnectorInterface | [string, object | undefined]
type ResolveIpfs = (ipfsIdentifier: string, path: string) => string

function normalizeConnectorConfig(
connector: ConnectorDeclaration
): [string, object] | null {
if (Array.isArray(connector)) {
return [connector[0], connector[1] || {}]
}
if (typeof connector === 'string') {
return [connector, {}]
}
return null
}

function getConnector(connector: ConnectorDeclaration): ConnectorInterface {
if (!Array.isArray(connector)) {
return connector
const normalizedConfig = normalizeConnectorConfig(connector)

if (normalizedConfig === null) {
return connector as ConnectorInterface
}

const [name, config = {}] = connector
const [name, config] = normalizedConfig

if (name === 'json') {
return new ConnectorJson(config as ConnectorJsonConfig)
Expand All @@ -30,28 +52,12 @@ function getConnector(connector: ConnectorDeclaration): ConnectorInterface {
throw new Error(`Unsupported connector name: ${name}`)
}

type ResolveIpfs = (ipfsIdentifier: string, path: string) => string
// type ResolveOrganization = (location: string) => Organization

export function Connect(
async function connect(
location: string,
{
connector,
ipfs,
ensRegistry,
}: {
connector: ConnectorDeclaration
ipfs?: ResolveIpfs
ensRegistry?: string
}
): Organization {
// TODO: Handle ENS names

connector: ConnectorDeclaration,
{ ipfs }: ConnectOptions = {}
): Promise<Organization> {
return new Organization(location, getConnector(connector))

// TODO: support several connections
// return (location: string): Organization =>
// new Organization(location, getConnector(connector))
}

export default Connect
export default connect
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Expand Up @@ -2,7 +2,7 @@ export { default as ConnectorEthereum } from 'plumbery-connector-ethereum'
export { default as ConnectorTheGraph } from 'plumbery-connector-thegraph'
export { ConnectorInterface } from './connections/ConnectorInterface'
export { default as ConnectorJson } from './connections/ConnectorJson'
export { default as Connect } from './connections/Connect'
export { default as connect } from './connections/connect'

// TODO: Use index.ts in src/wrappers instead?
export { default as Organization } from './entities/Organization'
Expand Down
12 changes: 9 additions & 3 deletions packages/organization-viewer-web/package.json
Expand Up @@ -4,14 +4,20 @@
"version": "1.0.0",
"private": true,
"scripts": {
"build": "npm run clean && npm run compile",
"clean": "rm -rf ./dist",
"dev": "yarn clean && webpack-dev-server",
"build": "yarn clean && webpack",
"clean": "rm -rf ./dist && rm -f tsconfig.tsbuildinfo",
"compile": "tsc -p tsconfig.json"
},
"dependencies": {
"plumbery-react": "*"
"@emotion/core": "^10.0.28",
"@types/react": "^16.9.35",
"plumbery-core": "*",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"devDependencies": {
"@types/react-dom": "^16.9.8",
"html-webpack-plugin": "^4.3.0",
"ts-loader": "^7.0.2",
"typescript": "^3.8.3",
Expand Down
136 changes: 136 additions & 0 deletions packages/organization-viewer-web/src/App.tsx
@@ -0,0 +1,136 @@
/** @jsx jsx */
import { useEffect, useState } from 'react'
import { css, jsx } from '@emotion/core'
import { connect } from 'plumbery-core'
import Main from './Main'
import OrgApps from './OrgApps'
import OrgInfo from './OrgInfo'
import OrgPermissions from './OrgPermissions'
import TextButton from './TextButton'
import { useCancellableAsync } from './generic-hooks'

const ORG_ADDRESSES_MAINNET_STAGING = new Map([
['piedao', '0x0c188b183ff758500d1d18b432313d10e9f6b8a4'],
])

const ORG_ADDRESSES_MAINNET = new Map([
['a1', '0x635193983512c621E6a3E15ee1dbF36f0C0Db8E0'],
])

const ORG_ADDRESSES_RINKEBY = new Map([
['org1', '0x0146414e5a819240963450332f647dfb7c722af4'],
['org2', '0x00018d22ece8b2ea4e9317b93f7dff67385693d8'],
['td.aragonid.eth', '0xa9Aad8e278eECf369c42F78D5A3f2d866DE902C8'],
['hive.aragonid.eth', '0xe520428C232F6Da6f694b121181f907931fD2211'],
['mesh.aragonid.eth', '0xa48300a4E89b59A79452Db7d3CD408Df57f4aa78'],
])

const ORG_ADDRESSES = ORG_ADDRESSES_MAINNET_STAGING

function addressFromOrgName(orgName: string) {
return (
ORG_ADDRESSES.get(orgName) ||
ORG_ADDRESSES.get(`${orgName}.aragonid.eth`) ||
orgName
)
}

function useRouting() {
const [orgName, setOrgName] = useState('')

const openOrg = (orgName: string) => {
window.location.hash = `/${orgName}`
}

const openApp = (appAddress: string) => {
window.location.hash = `/${orgName}/${appAddress}`
}

useEffect(() => {
const onChange = () => {
const org = window.location.hash.match(/^#\/([^\/]+)/)?.[1]
setOrgName(org || '')
}

onChange()
window.addEventListener('hashchange', onChange)

return () => {
window.removeEventListener('hashchange', onChange)
}
}, [])

return { orgName, openOrg, openApp }
}

export default function App() {
const { openOrg, openApp, orgName } = useRouting()

useEffect(() => {
openOrg([...ORG_ADDRESSES.keys()][0])
}, [])

const [org] = useCancellableAsync(
async () =>
connect(addressFromOrgName(orgName.trim()), [
'thegraph',
{
daoSubgraphUrl:
'https://api.thegraph.com/subgraphs/name/aragon/aragon-mainnet-staging',
},
]),
[orgName]
)

return (
<Main>
<label>
<div
css={css`
padding-left: 4px;
padding-bottom: 8px;
font-size: 20px;
`}
>
Enter an org location:
</div>
<input
onChange={event => openOrg(event.target.value)}
placeholder="e.g. xyz.aragonid.eth"
type="text"
value={orgName}
css={css`
width: 100%;
padding: 12px;
border: 2px solid #fad4fa;
border-radius: 6px;
font-size: 24px;
outline: 0;
`}
/>
</label>
<div
css={css`
white-space: nowrap;
padding-top: 8px;
padding-left: 4px;
font-size: 20px;
`}
>
Or pick one:&nbsp;
{[...ORG_ADDRESSES.keys()].map((name, index) => (
<span key={name}>
{index > 0 && <span>, </span>}
<TextButton onClick={() => openOrg(name)}>
{name.match(/^[^\.]+/)?.[0]}
</TextButton>
</span>
))}
.
</div>
<OrgInfo org={org} orgAddress={addressFromOrgName(orgName)} />
<OrgApps org={org} onOpenApp={openApp} />
<OrgPermissions org={org} />
</Main>
)
}

0 comments on commit 2540494

Please sign in to comment.