Skip to content

Commit

Permalink
Merge branch 'dev' into newstyle
Browse files Browse the repository at this point in the history
* dev:
  Dot voting demo fixes (#1176)
  Discussions (#1050)
  Fix linting and remove package-lock files
  • Loading branch information
chadoh committed Aug 24, 2019
2 parents 509fb28 + d19d263 commit b1fa9b6
Show file tree
Hide file tree
Showing 63 changed files with 1,925 additions and 134 deletions.
8 changes: 4 additions & 4 deletions apps/address-book/contracts/misc/Migrations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ pragma solidity ^0.4.24;
contract Migrations {
address public owner;
uint public lastCompletedMigration;

modifier restricted() {
if (msg.sender == owner) _; // solium-disable-line lbrace
}

constructor() public {
owner = msg.sender;
}

function setCompleted(uint completed) public restricted {
lastCompletedMigration = completed;
}

function upgrade(address newAddress) public restricted {
Migrations upgraded = Migrations(newAddress);
upgraded.setCompleted(lastCompletedMigration);
Expand Down
2 changes: 1 addition & 1 deletion apps/address-book/contracts/test/TestImports.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import "@tps/test-helpers/contracts/lib/bounties/StandardBounties.sol";


contract TestImports {
constructor() public {
constructor() public { // solium-disable-line no-empty-blocks
// to avoid lint error
}
}
8 changes: 4 additions & 4 deletions apps/allocations/contracts/misc/Migrations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ pragma solidity ^0.4.24;
contract Migrations {
address public owner;
uint public lastCompletedMigration;

modifier restricted() {
if (msg.sender == owner)
_;
}

constructor() public {
owner = msg.sender;
}

function setCompleted(uint completed) public restricted {
lastCompletedMigration = completed;
}

function upgrade(address newAddress) public restricted {
Migrations upgraded = Migrations(newAddress);
upgraded.setCompleted(lastCompletedMigration);
Expand Down
29 changes: 29 additions & 0 deletions apps/discussions/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"browsers": [
"> 1%",
"last 3 versions",
"ie >= 9",
"ios >= 8",
"android >= 4.2"
]
},
"useBuiltIns": false
}
]
],
"plugins": [
"@babel/plugin-proposal-class-properties",
[
"styled-components",
{
"displayName": true
}
]
]
}
6 changes: 6 additions & 0 deletions apps/discussions/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
app/node_modules
build
.cache
dist
coverage
36 changes: 36 additions & 0 deletions apps/discussions/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"standard",
"standard-react",
"prettier",
"prettier/react"
],
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 6,
"ecmaFeatures": {
"jsx": true
},
"sourceType": "module"
},
"plugins": ["prettier", "react"],
"rules": {
"valid-jsdoc": "error",
"react/prop-types": 0,
"prettier/prettier": [
"error",
{
"singleQuote": true,
"semi": false,
"trailingComma": "es5",
"bracketSpacing": true,
"jsxBracketSameLine": false
}
],
"linebreak-style": ["error", "unix"]
}
}
8 changes: 8 additions & 0 deletions apps/discussions/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
build
.cache
dist
ipfs.cmd
package-lock.json
coverage.json
coverage
15 changes: 15 additions & 0 deletions apps/discussions/.ipfsignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Git files
.gitignore

# Build files
.cache
node_modules
build
coverage

# Lock files
package-lock.json
yarn.lock

# Others
test
7 changes: 7 additions & 0 deletions apps/discussions/.solcover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
norpc: true,
copyPackages: [],
skipFiles: [
'test',
]
}
2 changes: 2 additions & 0 deletions apps/discussions/.soliumignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
contracts/Migrations.sol
35 changes: 35 additions & 0 deletions apps/discussions/.soliumrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"extends": "solium:all",
"plugins": [
"security"
],
"rules": {
"imports-on-top": ["error"],
"variable-declarations": ["error"],
"array-declarations": ["error"],
"operator-whitespace": ["error"],
"lbrace": ["error"],
"mixedcase": ["warning"],
"camelcase": ["error"],
"uppercase": ["warning"],
"no-empty-blocks": ["error"],
"no-unused-vars": ["error"],
"quotes": ["error"],
"indentation": ["error"],
"whitespace": ["error"],
"deprecated-suicide": ["error"],
"arg-overflow": ["error", 8],
"pragma-on-top": ["error"],
"security/enforce-explicit-visibility": ["error"],
"consequent": 0,
"error-reason": ["warning"],
"function-order": [
"error",
{
"ignore": {
"functions": ["initialize"]
}
}
]
}
}
102 changes: 102 additions & 0 deletions apps/discussions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Contextual Aragon Discussions

This repository is the starting point of contextual aragon discussions, it's composed of a few core components that a developer wishing to incorporate discussions needs to be aware of:

1. Contextual discussion smart contract - in charge of storing all the DAO's discussion data. Each discussion post is represented as an IPFS content hash to keep storage costs as efficient as possible.
2. `DiscussionsWrapper` component - a redux-like provider that provides discussion data through React context to all nested children and grandchildren.
3. `Discussion` component - a discussion thread component that displays all the discussion posts of a specific discussion thread and allows the user to take specific actions like post, hide, and revise.

The purpose of this readme is to document how all the moving parts are working together, how a developer could use this code in their own DAO, and what still needs to be done.

### Prerequisites

You first need to be running your contextual discussioned app + [aragon client](https://github.com/aragon/aragon) with a very specific version of aragon.js. You will need a version with the following 3 features:

1. [External transaction intents](https://github.com/aragon/aragon.js/pull/328)
2. [Ability to get information about the DAO's installed apps](https://github.com/aragon/aragon.js/pull/332)
3. [New forwarder API changes](https://github.com/aragon/aragon.js/pull/314)

We'd recommend running the latest master branch of the [aragon client](https://github.com/aragon/aragon).

These features should be included by default in aragon.js and aragon client come October 2019.

### Setup

##### Including the discussions app in your repo

If there is more demand for including contextual discussions in applications, we'll rip out the discussions app in this repo and publish it as a node module(s). For now, you should just copy and paste the `/apps/discussions` directory into your app, and look at the `apps/planning-suite-kit` as a reference for deploying it as a dependency for your own app.

##### Installing the discussions app

Here's a function we use to install the discussion app within the DAO:

```
function createDiscussionApp(address root, ACL acl, Kernel dao) internal returns (DiscussionApp app) {
bytes32 appId = apmNamehash("discussions");
app = DiscussionApp(dao.newAppInstance(appId, latestVersionAppBase(appId)));
app.initialize();
acl.createPermission(ANY_ENTITY, app, app.DISCUSSION_POSTER_ROLE(), root);
}
```

##### Setting up the discussions app as a forwarder

Every action that gets forwarded through the discussions app creates a new discussion thread. So we need to add the discussions app to the forwarding chain of any action we want to trigger a discussion. In this example, we have a new discussion created _before_ a new dot-vote gets created:

```
acl.createPermission(discussions, dotVoting, dotVoting.CREATE_VOTES_ROLE(), voting);
```
If discussions is the only app granted the `CREATE_VOTES_ROLE` permission, every new vote will have its own discussion thread.

When you send an intent to trigger the action that gets forwarded through the discussions app, you should see the appropriate radspec in the aragon client transaction confirmation panel.

##### Displaying and adding to discussion threads in the application frontend

You should first take the `discussions/app/modules/Discussions.js` component, and wrap your entire react tree inside of it. You need to pass your instance of [`AragonApp`](https://hack.aragon.org/docs/api-js-ref-api#examples) to it as well

```js
import { useAragonApi } from '@aragon/api-react'
import { Discussions } from 'discussions/app/modules/Discussions'

const App = () => {
const { api } = useAragonApi()
return {
<Discussions app={api}>
{...}
</Discussions>
}
}
```

Then, wherever you want to render a contextual discussion thread, you render the `Discussion` component, passing in a `discussionId` and the `ethereumAddress` of the logged in user:

```js
import { Discussion } from 'discussions/app/modules/Discussion'

const ComponentThatRendersDiscussionThread = ({ discussionId, ethereumAddress }) => {
return {
<Discussion discussionId={discussionId} ethereumAddress={ethereumAddress} />
}
}

```

Where does the `discussionId` come from? It's basically the unique identifier used by the Discussions App to keep track off all the threads it has created.

This id will be returned to your app context as a `ForwardedActions` event, similar to an external contract event, so it's important that it gets passed into the `<Discussion />` component successfully.

###### DiscussionId In-depth

The `discussionId` is a `Number` that represents the relative order in which this specific transaction intent was forwarded through the discussion app. For example, let's say you had 5 transactions that were forwarded through the discussion app - the discussionId relative to these 5 transactions is the order in which they occured. It could be:

1, 2, 3, 4, 5 or 14, 15, 16, 19, 20. The only thing that matters is the _order_ the transactions occured. The discussion app will figure out the rest for you.


##### How this all works under the hood

The discussions app generates a new discussion thread every time an action gets successfully forwarded. When the discussions app script loads, it uses the latest [forwarder api](https://github.com/aragon/aragon.js/pull/314) to [keep track of which discussion threads belong to which app](https://github.com/AutarkLabs/planning-suite/blob/discussions/apps/discussions/app/script.js#L36).

On the frontend, the `Discussions.js` component senses when the handshake has been established between the front-end and client. [Once it does](https://github.com/AutarkLabs/planning-suite/blob/discussions/apps/discussions/app/modules/Discussions.js#L26), it initializes a new instance of the [`DiscussionApi`](https://github.com/AutarkLabs/planning-suite/blob/discussions/apps/discussions/app/modules/DiscussionsApi.js).

The discussionApi is responsible for [keeping track of all the discussion threads and posts for your app](https://github.com/AutarkLabs/planning-suite/blob/discussions/apps/discussions/app/modules/DiscussionsApi.js#L136). Its also equipped with methods to [post](https://github.com/AutarkLabs/planning-suite/blob/discussions/apps/discussions/app/modules/DiscussionsApi.js#L162), [hide](https://github.com/AutarkLabs/planning-suite/blob/discussions/apps/discussions/app/modules/DiscussionsApi.js#L197), and [revise](https://github.com/AutarkLabs/planning-suite/blob/discussions/apps/discussions/app/modules/DiscussionsApi.js#L175) Discussion Posts.

41 changes: 41 additions & 0 deletions apps/discussions/app/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import styled from 'styled-components'
import { EmptyStateCard, GU, Header, Main, breakpoint } from '@aragon/ui'
import nothingToSeeHere from './empty-state.png'

const illustration = <img src={nothingToSeeHere} alt="" height="160" />

function App() {
return (
<Main>
<Header primary="Discussions Proof-of-Concept" />
<Content>
Other apps used by your organization can embed on-chain discussions,
thanks to the coordinating work this app does in the background.
Someday, you may see a dashboard here, showing all your ongoing
discussions.
</Content>
<Spacer>
<EmptyStateCard
text="This is a background app. Nothing to see here."
onActivate={() => <div />}
illustration={illustration}
/>
</Spacer>
</Main>
)
}

const Spacer = styled.div`
display: flex;
justify-content: center;
margin-top: ${4 * GU}px;
`

const Content = styled.p`
padding-left: ${2 * GU}px;
padding-right: ${2 * GU}px;
${breakpoint('medium', 'padding-left: 0; padding-right: 0;')}
`

export default App
Binary file added apps/discussions/app/empty-state.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions apps/discussions/app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Aragon App</title>
</head>
<body>
<div id="root"></div>
<script src="./index.js"></script>
</body>
</html>
17 changes: 17 additions & 0 deletions apps/discussions/app/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { AragonApi } from '@aragon/api-react'
import App from './App'
import { initialState } from './state'

const reducer = state => {
if (state === null || Object.keys(state).length === 0) return initialState
return state
}

ReactDOM.render(
<AragonApi reducer={reducer}>
<App />
</AragonApi>,
document.getElementById('root')
)
7 changes: 7 additions & 0 deletions apps/discussions/app/ipfs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import ipfsClient from 'ipfs-http-client'

export const ipfs = ipfsClient({
host: 'ipfs.autark.xyz',
port: '5001',
protocol: 'https',
})

0 comments on commit b1fa9b6

Please sign in to comment.