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

Update org viewer #24

Merged
merged 18 commits into from May 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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",
Copy link
Contributor

Choose a reason for hiding this comment

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

👀👀

Copy link
Contributor Author

Choose a reason for hiding this comment

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

haha yes, I wanted to test its JSX pragma for css: https://emotion.sh/docs/css-prop#jsx-pragma

Other than that it’s basically styled-components 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

Haha yeah, I've used emotion before, this option looks cool for inline styling as well! 😃

"@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>
)
}