Skip to content
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

Frontend on heroku #5

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5b95b69
Procfile & static.json for create-react-app-buildpack
aCandidMind Sep 22, 2017
7f21529
Move web/ top-level to comply with buildpack mars/create-react-app-bu…
aCandidMind Sep 22, 2017
a23614d
Set root for nginx explictly in static.json
aCandidMind Sep 22, 2017
7a5d902
Switch to production URLs in the frontend, use a better german text &…
aCandidMind Sep 22, 2017
b373bc2
Enable /politicians routes, remove react logo & minor corrections
aCandidMind Sep 22, 2017
9e2c472
Fix routes config in static.json
aCandidMind Sep 22, 2017
004ae60
Add Code for Frankfurt logo and a sources footer. Further minor improvs.
aCandidMind Sep 22, 2017
ce1b43e
Fix logo import in App.js & add proper page title
aCandidMind Sep 22, 2017
3a19539
Deactive spinning logo animation and format footer & give it some whi…
aCandidMind Sep 22, 2017
f2546e5
Deactivate wordcluster and retweet graphs & improve homepage text
aCandidMind Sep 22, 2017
f2bccb6
Make profile picture a column left of the info & the graphs have thei…
aCandidMind Sep 22, 2017
b0835e9
Fix logo and footer stylying plus better wording for follower counts
aCandidMind Sep 22, 2017
b667389
Add photo or placeholder pic to MemberPage
aCandidMind Sep 22, 2017
6186ec8
Previous commit was missing Portrait_placeholder.png file
aCandidMind Sep 22, 2017
233cfad
Slightly improved layout for member page
jschirrmacher Sep 22, 2017
994315a
Add pic prio logic (1. wikimedia, 2. abgw via archive.org, 3. first i…
aCandidMind Sep 22, 2017
f75c474
Extend profile pic prio logic with twitter pic as 4th choice, see ...
aCandidMind Sep 22, 2017
7e48531
Prevent profile pic from filling up whole width in xs grid (mobile)
aCandidMind Sep 22, 2017
fe2be21
More design improvements on member page
jschirrmacher Sep 22, 2017
674d219
More design improvements on member page
jschirrmacher Sep 22, 2017
ab36b57
Better alignment for title
jschirrmacher Sep 22, 2017
1b8b652
Party page :-)
jschirrmacher Sep 22, 2017
b07e924
As we only have candidate data for the big six parties, only show them
aCandidMind Sep 22, 2017
10a89d2
Use the new pluralized API URLs in the frontend
aCandidMind Sep 23, 2017
8d143da
Add names to districts, they are now also sorted by the name
aCandidMind Sep 23, 2017
de85583
Render candidate names on index with last name first and party to eac…
aCandidMind Sep 23, 2017
89fa502
Sort list of candidates by last name
aCandidMind Sep 23, 2017
4e539ea
Page for districts
jschirrmacher Sep 23, 2017
156952b
Show candidates of party and party name on PartyPage
aCandidMind Sep 23, 2017
d148a1f
Fixed handling of candidates on PartyPage
jschirrmacher Sep 23, 2017
d0f4765
Show logo on party page
aCandidMind Sep 23, 2017
a3d5083
Show candidates on district page
aCandidMind Sep 23, 2017
2af647e
Show display name for party on MemberPage
aCandidMind Sep 23, 2017
01168d3
Convert party to link and add district link and list place
aCandidMind Sep 23, 2017
ce9aa82
Readability in main page
jschirrmacher Sep 23, 2017
4e6f91f
Added link to github page
jschirrmacher Sep 23, 2017
3de0dbe
Back button goes back one page and not to main page any more
jschirrmacher Sep 23, 2017
f1a2865
More explanations in Jumbotron.
jschirrmacher Sep 23, 2017
7b17af9
Make parties and districts deep-linkable in production as well
aCandidMind Sep 23, 2017
acb571e
Cleanup
jschirrmacher Oct 7, 2017
0280466
Updated dependencies
jschirrmacher Oct 7, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,17 @@ dev_*.ipynb
.DS_Store
node_modules/
polbotcheck/dump/
web/Release.key
Release.key
polbotcheck/__pycache__/


# testing
/coverage

# production
/build

# misc
npm-debug.log*
yarn-debug.log*
yarn-error.log*
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
web: FLASK_ENV=production webapi/run_analytics_server.sh
web: bin/boot
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 6 additions & 6 deletions web/package.json → package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
"private": true,
"dependencies": {
"chart.js": "^2.5.0",
"react": "^15.4.2",
"react-bootstrap": "^0.30.7",
"react-chartjs-2": "^2.0.5",
"react-dom": "^15.4.2",
"react-router": "^2.8.1"
"react": "^16.0.0",
"react-bootstrap": "^0.31.3",
"react-chartjs-2": "^2.6.4",
"react-dom": "^16.0.0",
"react-router-dom": "^4.2.2"
},
"devDependencies": {
"react-scripts": "0.9.3"
"react-scripts": "^1.0.14"
},
"scripts": {
"start": "react-scripts start",
Expand Down
2 changes: 1 addition & 1 deletion polbotcheck/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from config.db_credentials import db_credentials

CANDIDATES_PATH = '../web/public/candidates.json'
CANDIDATES_PATH = '../public/candidates.json'


# Configure your ArangoDB server connection here
Expand Down
File renamed without changes.
Binary file added public/favicon.ico
Binary file not shown.
2 changes: 1 addition & 1 deletion web/public/index.html → public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>BotOrNot hessische BTW-Kandidaten 2017</title>
</head>
<body>
<div id="root"></div>
Expand Down
106 changes: 106 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
@import '//maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css';

body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

.App-logo {
float: left;
}

.App, h3 {
text-align: center;
}

.App h2 {
font-size: 28px;
}

.jumbotron p {
font-size: 16px;
}

.dl, .dd {
display: inline;
}

.dd {
text-decoration: underline;
cursor: pointer;
}

.dt {
display: none;
position: absolute;
background: #ffe;
font-size: 13px;
font-weight: normal;
text-align: left;
padding: 5px;
max-width: 320px;
}

.dl:hover .dt {
display: inline-block;
}

.App-header {
background-color: #222;
padding: 20px;
color: white;
}

.Profile-picture {
display: inline-block;
max-width: 240px;
max-height: 240px;
vertical-align: top;
}

.App-profile {
display: inline-block;
margin-left: 20px;
min-width: 300px;
}

.App-info {
margin: 1em -5px;
padding: 0;
}

.App-info > div {
padding: 5px;
}

.App-info > div > div {
padding: 5px;
border: 1px solid #ccc;
border-radius: 4px;
}

img {
max-width: 100%;
}

ul {
text-align: left;
}

.Info-diagram {
padding: 2em;
}

.Info-diagram img {
max-width: 66%;
}

.number {
float: right;
}

#footer {
margin-top: 3em;
text-align: left;
}
147 changes: 147 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React, {Component} from 'react'
import { Link } from 'react-router-dom'
import {Grid, Row, Col, Jumbotron, Panel} from 'react-bootstrap'

import './App.css'
import Title from './Title'
import { parties, getFullName } from './Utils'

class App extends Component {

state = {
politicians: [],
parties: parties,
districts: {districts: []}
};

load(what, url) {
let self = this;
fetch(url)
.then(res => res.json())
.then(data => {
self.state[what] = data.data || data;
self.setState(self.state);
})
.catch(e => console.log(e))
}

componentWillMount() {
this.load('districts', 'https://botornot-hessen-api.herokuapp.com/pbc/districts');
this.load('politicians', '/candidates.json');
}

getCandidateEntry(candidate) {
const fullName = getFullName(candidate.name);
return fullName.concat(" (" + this.state.parties[candidate.election.party] + ")");
}

render() {
const politicians = this.state.politicians.sort((a, b) => a.name.surname.localeCompare(b.name.surname));
const parties = this.state.parties;
const districts = this.state.districts.districts;
return (
<Grid className="App">
<Title />
<Row>
<Jumbotron>
<h2>
Wer folgt unseren Bundestagswahl-Kandidaten wirklich?
</h2>
<p>Sind es Bots oder echte Menschen, die den Kandidaten folgen? Das finden wir hier für euch heraus,
damit klar wird, was echt ist und was nicht.
</p>

<h3>Was sind das für Bots, um die es geht?</h3>
<p>
Twitter Bots sind computergesteuerte Programme die sich automatisiert auf dem Social Network bewegen
und verschiedene Ziele verfolgen. Die meisten betreiben Marketing und folgen allen möglichen
Accounts und erzeugen so Aufmerksamkeit. Andere sorgen mit Retweets für eine größere
Reichweite anderer Accounts, die sie voranbringen wollen.
</p>
<p>
Eigentlich ist es noch kein Problem, wenn Bots dem Account eines Kandidaten folgen. Problematisch wird
es, wenn wir als Wähler die Meldungen eines Kandidaten häufiger zu sehen bekommen, weil sie angeblich
viele andere interessieren (und daher auch mich interessieren könnten), aber hauptsächlich Bots diese
Retweets erzeugt haben.
</p>

<h3>Und wie erkenne ich, welche Retweets "echt" sind, und was von Bots künstlich erzeugt wird?</h3>
<p>
Wir geben dir einen Einblick, wieviele Retweets der Politiker von Bots sind, um besser einschätzen zu können,
ob nachgeholfen wird. Zur Erkennung ob ein Twitter Account ein Bot ist, verlassen wir uns auf die
mehrjährige Recherche-Arbeit des <span className="dl"><span className="dd">Projekts&nbsp;
<a href="http://truthy.indiana.edu/botornot/">Botometer</a></span><span className="dt">früher Truthy BotOrNot</span></span>.
Es wurde 2014 von der Indiana University aus den USA ins Leben gerufen. Über deren Web-Dienst kann man die
Wahrscheinlichkeit einholen, mit der ein Twitter Account ein Bot ist.
</p>
<p>
Wir gehen ab 70% Prozent davon aus, dass es sich um einen Bot handelt.
</p>
<p>
Für jeden <span className="dl"><span className="dd">Politiker, zu dem wir die Daten einholen konnten</span>
<span className="dt">die Datenlage ist recht
eingeschränkt, siehe Quellen ganz unten, daher auch die fehlenden Parteien und Wahlkreise</span></span>&nbsp;
zeigen wir hier nun den <strong>Anteil der Follower die Bots sind, den Anteil der Retweets die von Bots
stammen und wieviel Prozent der Retweeter Bots sind.</strong>
</p>
</Jumbotron>
</Row>
<Row>
<Col md={4}>
<h3>Politiker</h3>
<ul>
{politicians.map(function(value) {
return <li key={value.id}>
<Link to={'/politicians/' + value.slug}>
{this.getCandidateEntry(value)}</Link></li>
},this)}
</ul>
</Col>
<Col md={4}>
<h3>Parteien</h3>
<ul>
{Object.keys(parties).map(function(key) {
const value = parties[key];
return <li key={key}><Link to={'/parties/' + key}>{value}</Link></li>;
})}
</ul>
</Col>
<Col md={4}>
<h3>Wahlkreise</h3>
<ul>
{districts.map(function(districtObject) {
return <li key={districtObject.id}><Link to={'/districts/' + districtObject.id}>{districtObject.name}</Link></li>;
})}
</ul>
</Col>
</Row>
<Row>
<Panel id="footer" header="Quellen">
<p>
Die Liste der Politiker und die Infos über sie, u.a. die Account-Namen der Twitter-Profile haben wir über&nbsp;
<a href="https://github.com/okfde/wahldaten/tree/master/kandidierende">github.com/okfde/wahldaten</a> eingeholt.
</p>
<p>
Die Wahrscheinlichkeit ob ein Account ein Bot ist, holen wir uns über&nbsp;
<a href="http://truthy.indiana.edu/botornot/">Botometer</a> (früher Truthy BotOrNot) von der
Indiana University aus den USA ein.
</p>
<p>
Die Follower und Retweet Daten sind über die <a href="https://dev.twitter.com/rest/public">Twitter-API</a>
&nbsp;von Twitter selbst eingeholt worden.
</p>
<p>
Die Namen der Wahlkreise haben wir über die&nbsp;
<a href="https://www.abgeordnetenwatch.de/api/">Abgeordnetenwatch API</a> abgerufen.
</p>
<p>Der Quellcode dieses Projekts ist zu finden auf&nbsp;
<a href="https://github.com/codeforfrankfurt/PolBotCheck">github</a>.
</p>
</Panel>
</Row>
</Grid>
);
}
}

export default App;
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions src/Breadcrumbs.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.breadcrumbs {
margin-bottom: 1em;
}
14 changes: 14 additions & 0 deletions src/Breadcrumbs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React, {Component} from 'react'
import { withRouter } from 'react-router-dom'
import './Breadcrumbs.css'

class Breadcrumbs extends Component {
render() {
return (<div className="breadcrumbs">
<button onClick={this.props.history.goBack} className="btn btn-default">« Zurück</button>
</div>
)
}
}

export default withRouter(Breadcrumbs)
59 changes: 59 additions & 0 deletions src/DistrictPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, {Component} from 'react'
import Breadcrumbs from './Breadcrumbs'
import { Link } from 'react-router-dom'
import {Row, Col, Panel} from 'react-bootstrap'
import Title from './Title'
import { parties, getFullName } from './Utils'

class DistrictPage extends Component {
state = {
district: {
name: '',
candidates: []
}
}

componentWillMount() {
let self = this;
const url = 'https://botornot-hessen-api.herokuapp.com/pbc/districts/' + this.props.match.params.slug;
fetch(url, {mode: 'cors', headers: {'Accept': 'application/json'}})
.then(res => {
return res.json().then(data => {
self.state = data;
console.log(data);
self.setState(self.state);
});
})
.catch(e => console.log("error fetching " + url, e));
}

render() {
let candidates = this.state.district.candidates.map(candidate => {
const fullName = getFullName(candidate.name);
const party = parties[candidate.election.party];
return (
<li key={candidate.id}>
<Link to={'/politicians/' + candidate.slug}>{fullName + " (" + party + ") "}</Link>
</li>
);
}, this)

return (<div className="container">
<Title />
<Breadcrumbs />

<Panel bsStyle="primary" className="App-profile" bsSize="large">
<p>Wahlkreis: {this.state.district.name ? this.state.district.name : '-'}</p>
</Panel>

<Row className="App-info">
<Col xs={12}>
<h3>Mitglieder im Wahlkreis</h3>
{candidates}
</Col>
</Row>
</div>)
}
}

export default DistrictPage;
Loading