Merge branch 'master' into chore/add_coffeelint_config

This commit is contained in:
BertoCQ
2017-06-16 18:28:31 +02:00
committed by GitHub
66 changed files with 4045 additions and 1902 deletions

211
.scss-lint.yml Normal file
View File

@@ -0,0 +1,211 @@
linters:
BangFormat:
enabled: true
space_before_bang: true
space_after_bang: false
BorderZero:
enabled: true
convention: zero
ChainedClasses:
enabled: false
ColorKeyword:
enabled: true
ColorVariable:
enabled: false
Comment:
enabled: false
DebugStatement:
enabled: true
DeclarationOrder:
enabled: true
DisableLinterReason:
enabled: true
DuplicateProperty:
enabled: false
ElsePlacement:
enabled: true
style: same_line
EmptyLineBetweenBlocks:
enabled: true
ignore_single_line_blocks: true
EmptyRule:
enabled: true
ExtendDirective:
enabled: false
FinalNewline:
enabled: true
present: true
HexLength:
enabled: true
style: short
HexNotation:
enabled: true
style: lowercase
HexValidation:
enabled: true
IdSelector:
enabled: true
ImportantRule:
enabled: false
ImportPath:
enabled: true
leading_underscore: false
filename_extension: false
Indentation:
enabled: true
allow_non_nested_indentation: true
character: space
width: 2
LeadingZero:
enabled: true
style: include_zero
MergeableSelector:
enabled: false
force_nesting: false
NameFormat:
enabled: true
convention: hyphenated_lowercase
allow_leading_underscore: true
NestingDepth:
enabled: true
max_depth: 4
PlaceholderInExtend:
enabled: true
PrivateNamingConvention:
enabled: true
prefix: _
PropertyCount:
enabled: false
PropertySortOrder:
enabled: false
PropertySpelling:
enabled: true
extra_properties: []
PropertyUnits:
enabled: false
PseudoElement:
enabled: true
QualifyingElement:
enabled: true
allow_element_with_attribute: false
allow_element_with_class: false
allow_element_with_id: false
SelectorDepth:
enabled: true
max_depth: 5
SelectorFormat:
enabled: true
convention: hyphenated_lowercase
Shorthand:
enabled: true
SingleLinePerProperty:
enabled: true
allow_single_line_rule_sets: false
SingleLinePerSelector:
enabled: true
SpaceAfterComma:
enabled: true
SpaceAfterPropertyColon:
enabled: true
style: one_space
SpaceAfterPropertyName:
enabled: true
SpaceAfterVariableColon:
enabled: true
style: at_least_one_space
SpaceAfterVariableName:
enabled: true
SpaceAroundOperator:
enabled: true
style: one_space
SpaceBeforeBrace:
enabled: true
style: space
allow_single_line_padding: true
SpaceBetweenParens:
enabled: true
spaces: 0
StringQuotes:
enabled: true
style: single_quotes
TrailingSemicolon:
enabled: true
TrailingZero:
enabled: true
TransitionAll:
enabled: false
UnnecessaryMantissa:
enabled: true
UnnecessaryParentReference:
enabled: true
UrlFormat:
enabled: false
UrlQuotes:
enabled: true
VariableForProperty:
enabled: false
VendorPrefixes:
enabled: true
identifier_list: base
include: []
exclude: []
ZeroUnit:
enabled: true

View File

@@ -71,6 +71,9 @@ gem 'rails-assets-markdown-it', source: 'https://rails-assets.org'
gem 'cocoon'
gem 'graphql', '~> 1.6.3'
gem 'graphiql-rails', '~> 1.4.1'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
@@ -105,7 +108,9 @@ end
group :development do
# Access an IRB console on exception pages or by using <%= console %> in views
gem 'scss_lint', require: false
gem 'web-console', '3.3.0'
end
eval_gemfile './Gemfile_custom'

View File

@@ -176,8 +176,10 @@ GEM
geocoder (1.4.3)
globalid (0.3.7)
activesupport (>= 4.1.0)
graphiql-rails (1.4.1)
rails
graphql (1.6.3)
groupdate (3.2.0)
activesupport (>= 3)
gyoku (1.3.1)
builder (>= 2.1.2)
hashie (3.5.5)
@@ -395,6 +397,9 @@ GEM
nokogiri (>= 1.4.0)
nori (~> 2.4)
wasabi (~> 3.4)
scss_lint (0.53.0)
rake (>= 0.9, < 13)
sass (~> 3.4.20)
simplecov (0.14.1)
docile (~> 1.1.0)
json (>= 1.8, < 3)
@@ -504,6 +509,8 @@ DEPENDENCIES
foundation-rails (~> 6.2.4.0)
foundation_rails_helper (~> 2.0.0)
fuubar
graphiql-rails (~> 1.4.1)
graphql (~> 1.6.3)
groupdate (~> 3.2.0)
i18n-tasks (~> 0.9.15)
initialjs-rails (= 0.2.0.4)
@@ -537,6 +544,7 @@ DEPENDENCIES
rvm1-capistrano3
sass-rails (~> 5.0, >= 5.0.4)
savon
scss_lint
sitemap_generator (~> 5.3.1)
social-share-button (~> 0.10)
spring

View File

@@ -47,7 +47,6 @@ RAILS_ENV=test rake db:setup
Run the app locally:
```
bin/rails s
```
Prerequisites for testing: install PhantomJS >= 1.9.8
@@ -58,7 +57,13 @@ Run the tests with:
bin/rspec
```
Run the [coffeelint](http://www.coffeelint.org/) linter for coffeescript (install with `npm install -g coffeelint`) :
If you add SCSS code you can check it with:
```
scss-lint
```
If you work on Coffeescript code you can check it with [coffeelint](http://www.coffeelint.org/) (install with `npm install -g coffeelint`) :
```
coffeelint .

View File

@@ -58,7 +58,13 @@ Para ejecutar los tests:
bin/rspec
```
Usa el linter coffeescript [coffeelint](http://www.coffeelint.org/) (puedes instalaro con `npm install -g coffeelint`) :
Si añades código SCSS puedes revisarlo con:
```
scss-lint
```
Si trabajas en código coffeescript puedes revisarlo con [coffeelint](http://www.coffeelint.org/) (instalalo con `npm install -g coffeelint`) :
```
coffeelint .

View File

@@ -53,53 +53,53 @@ $small-font-size: rem-calc(14);
$line-height: rem-calc(24);
$tiny-font-size: rem-calc(12);
$brand: #004A83;
$brand: #004a83;
$dark: darken($brand, 10%);
$text: #222222;
$text: #222;
$text-medium: #515151;
$text-light: #BFBFBF;
$text-light: #bfbfbf;
$border: #DEE0E3;
$border: #dee0e3;
$link: $brand;
$link-hover: darken($link, 20%);
$debates: $brand;
$like: #7BD2A8;
$unlike: #EF8585;
$like: #7bd2a8;
$unlike: #ef8585;
$delete: #F04124;
$check: #46DB91;
$delete: #f04124;
$check: #46db91;
$proposals: #FFA42D;
$proposals: #ffa42d;
$proposals-dark: #794500;
$budget: #7E328A;
$budget-hover: #7571BF;
$budget: #7e328a;
$budget-hover: #7571bf;
$highlight: #E7F2FC;
$light: #F5F7FA;
$featured: #FFDC5C;
$highlight: #e7f2fc;
$light: #f5f7fa;
$featured: #ffdc5c;
$footer-border: #BFC1C3;
$footer-border: #bfc1c3;
$success-bg: #DFF0D8;
$success-border: #D6E9C6;
$color-success: #3C763D;
$success-bg: #dff0d8;
$success-border: #d6e9c6;
$color-success: #3c763d;
$info-bg: #D9EDF7;
$info-border: #BCE8F1;
$color-info: #31708F;
$info-bg: #d9edf7;
$info-border: #bce8f1;
$color-info: #31708f;
$warning-bg: #FCF8E3;
$warning-border: #FAEBCC;
$color-warning: #8A6D3B;
$warning-bg: #fcf8e3;
$warning-border: #faebcc;
$color-warning: #8a6d3b;
$alert-bg: #F2DEDE;
$alert-border: #EBCCD1;
$color-alert: #A94442;
$alert-bg: #f2dede;
$alert-border: #ebccd1;
$color-alert: #a94442;
// 1. Global
@@ -118,8 +118,8 @@ $foundation-palette: (
$light-gray: #e6e6e6;
$medium-gray: #cacaca;
$dark-gray: #8a8a8a;
$black: #222222;
$white: #ffffff;
$black: #222;
$white: #fff;
$body-background: $white;
$body-font-color: $black;
$body-font-family: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif !important;
@@ -573,7 +573,7 @@ $tab-background: $white;
$tab-background-active: $white;
$tab-item-font-size: $base-font-size;
$tab-item-background-hover: $white;
$tab-item-padding: $line-height/2 0;
$tab-item-padding: $line-height / 2 0;
$tab-expand-max: 6;
$tab-content-background: $white;
$tab-content-border: $border;

View File

@@ -13,22 +13,22 @@
// 01. Global styles
// -----------------
$admin-color: #CF3638;
$admin-color: #cf3638;
body.admin {
.admin {
header {
.header {
border: 0;
}
.top-links {
background: darken($admin-color, 15%);
}
.back-web {
padding-top: $line-height/4;
padding-top: $line-height / 4;
text-decoration: underline;
}
}
.top-bar {
background: $admin-color !important;
@@ -42,34 +42,17 @@ body.admin {
}
}
form {
.button {
margin-top: 0;
&.margin-top {
margin-top: $line-height;
}
}
input[type="text"], textarea {
width: 100%;
}
.fieldset {
select {
height: $line-height*2;
height: $line-height * 2;
}
.input-group input[type="text"] {
[type="text"] {
border-radius: 0;
margin-bottom: 0 !important;
}
}
}
table {
th {
text-align: left;
@@ -84,12 +67,12 @@ body.admin {
}
&.with-button {
line-height: $line-height*2;
line-height: $line-height * 2;
}
}
tr {
background: white;
background: #fff;
border: 1px solid $border;
&:hover {
@@ -97,7 +80,8 @@ body.admin {
}
}
td.break {
table {
.break {
word-break: break-word;
}
@@ -105,14 +89,15 @@ body.admin {
table-layout: fixed;
}
input[type="submit"] ~ a, a ~ a {
[type="submit"] ~ a,
a ~ a {
margin-left: 0;
margin-right: 0;
margin-top: $line-height/2;
margin-top: $line-height / 2;
@include breakpoint(medium) {
margin-left: $line-height/2;
margin-right: $line-height/2;
margin-left: $line-height / 2;
margin-right: $line-height / 2;
margin-top: 0;
}
}
@@ -122,7 +107,7 @@ body.admin {
max-width: none;
}
.menu.simple li.active {
.menu.simple .active {
border-bottom: 2px solid $admin-color;
color: $admin-color;
}
@@ -132,10 +117,6 @@ body.admin {
padding-right: 0;
}
#proposals {
width: 100% !important;
}
.accordion-title {
font-size: $base-font-size;
}
@@ -183,7 +164,7 @@ body.admin {
&:hover .on-hover-block {
display: block;
margin: 0;
margin-top: $line-height/2;
margin-top: $line-height / 2;
width: 100%;
}
}
@@ -231,20 +212,21 @@ body.admin {
display: inline-block;
font-size: rem-calc(24);
line-height: $line-height;
padding: $line-height/2 $line-height/4;
padding: $line-height / 2 $line-height / 4;
padding-left: 0;
vertical-align: middle;
}
}
li {
background: white;
background: #fff;
margin: 0;
outline: 0;
ul {
margin-left: $line-height/1.5;
margin-left: $line-height / 1.5;
border-left: 1px solid $border;
padding-left: $line-height/2;
padding-left: $line-height / 2;
}
&.section-title {
@@ -254,8 +236,6 @@ body.admin {
&.active a {
background: #f3f6f7;
border-radius: rem-calc(6);
-moz-border-radius: rem-calc(6);
-webkit-border-radius: rem-calc(6);
color: $admin-color;
font-weight: bold;
}
@@ -271,17 +251,14 @@ body.admin {
&:hover {
background: #f3f6f7;
border-radius: rem-calc(6);
-moz-border-radius: rem-calc(6);
-webkit-border-radius: rem-calc(6);
color: $admin-color;
text-decoration: none;
}
}
}
.is-accordion-submenu-parent {
& > a::after {
> a::after {
border-color: $admin-color transparent transparent;
}
}
@@ -291,11 +268,11 @@ body.admin {
margin-left: $line-height;
li:first-child {
padding-top: $line-height/2;
padding-top: $line-height / 2;
}
li:last-child {
padding-bottom: $line-height/2;
padding-bottom: $line-height / 2;
}
a {
@@ -308,12 +285,14 @@ body.admin {
// -----------------
.delete {
border-bottom: 1px dotted #CF2A0E;
border-bottom: 1px dotted #cf2a0e;
color: $delete;
font-size: $small-font-size;
&:hover, &:active, &:focus {
border-bottom: 1px dotted white;
&:hover,
&:active,
&:focus {
border-bottom: 1px dotted #fff;
color: #cf2a0e;
}
}
@@ -376,8 +355,6 @@ body.admin {
&:hover {
max-height: rem-calc(1000);
transition: max-height 0.9s;
-moz-transition: max-height 0.9s;
-webkit-transition: max-height 0.9s;
}
}
@@ -385,7 +362,7 @@ body.admin {
// ---------
.stats {
background: white;
background: #fff;
}
.stats-numbers {
@@ -417,6 +394,7 @@ body.admin {
ul {
list-style-type: none;
margin-left: 0;
}
li {
font-size: rem-calc(14);
@@ -435,16 +413,17 @@ body.admin {
color: $delete;
}
}
}
}
.account-info, .login-as, .spending-proposal-info {
.account-info,
.login-as,
.spending-proposal-info {
background-color: #e7e7e7;
border-radius: rem-calc(3);
font-size: rem-calc(16);
font-weight: normal;
margin: $line-height;
padding: $line-height/2;
padding: $line-height / 2;
strong {
font-size: rem-calc(18);
@@ -455,15 +434,17 @@ body.admin {
margin-bottom: 0;
}
body.admin {
.admin {
.investment-projects-list.medium-9 {
width: 100%;
}
}
.investment-projects-summary {
.investment-projects-summary {
th, td {
th,
td {
text-align: center;
&:first-child {
@@ -485,10 +466,10 @@ body.admin {
background: $success-bg;
}
}
}
}
.admin-content .select-geozone, .admin-content .select-heading {
.admin-content .select-geozone,
.admin-content .select-heading {
a {
display: block;
@@ -501,14 +482,14 @@ body.admin {
}
}
table.investment-projects-summary {
.investment-projects-summary {
td.total-price {
.total-price {
white-space: nowrap;
}
}
body.admin {
.admin {
.geozone {
background: #ececec;
@@ -516,8 +497,8 @@ body.admin {
color: $text;
display: inline-block;
font-size: $small-font-size;
margin-bottom: $line-height/3;
padding: $line-height/4 $line-height/3;
margin-bottom: $line-height / 3;
padding: $line-height / 4 $line-height / 3;
text-decoration: none;
&:hover {
@@ -538,9 +519,9 @@ body.admin {
table {
.callout {
height: $line-height*2;
line-height: $line-height*2;
padding: 0 $line-height/2;
height: $line-height * 2;
line-height: $line-height * 2;
padding: 0 $line-height / 2;
}
}
@@ -551,15 +532,15 @@ table {
// ---------------
.markdown-editor {
background-color: white;
background-color: #fff;
.markdown-area,
#markdown-preview {
.markdown-preview {
display: none;
}
}
.markdown-editor #markdown-preview {
.markdown-editor .markdown-preview {
overflow-y: auto;
height: 15em;
}
@@ -577,11 +558,11 @@ table {
left: 0;
}
.markdown-editor.fullscreen #markdown-preview {
.markdown-editor.fullscreen .markdown-preview {
height: 99%;
}
.edit_legislation_draft_version .row {
.edit-legislation-draft-version .row {
margin-bottom: 2rem;
}
@@ -614,7 +595,7 @@ table {
// 08. CMS
// --------------
.cms_page_list {
.cms-page-list {
[class^="icon-"] {
padding-right: $menu-icon-spacing;
@@ -624,17 +605,18 @@ table {
.legislation-process-edit {
.edit_legislation_process {
.edit-legislation-process {
small {
color: $text-medium;
}
input[type]:not([type="submit"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]) {
[type]:not([type="submit"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]) {
background: $white;
}
.legislation-process-start, .legislation-process-end {
.legislation-process-start,
.legislation-process-end {
@include breakpoint(medium) {
line-height: 3rem;
}
@@ -664,7 +646,7 @@ table {
.legislation-questions-form {
input[type]:not([type="submit"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]) {
[type]:not([type="submit"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]) {
background: $white;
margin-bottom: 0;
@@ -673,27 +655,16 @@ table {
}
}
input::-webkit-input-placeholder {
font-style: italic;
}
input:-moz-placeholder { /* Firefox 18- */
font-style: italic;
}
input::-moz-placeholder { /* Firefox 19+ */
font-style: italic;
}
input:-ms-input-placeholder {
input::placeholder {
font-style: italic;
}
.legislation-questions-answers {
margin-bottom: 1rem;
}
}
.field {
.field {
margin-bottom: 1rem;
@include breakpoint(medium) {
@@ -720,115 +691,24 @@ table {
text-decoration: none;
}
}
}
}
.legislation-draft-versions-form {
.legislation-process-version {
@include breakpoint(medium) {
text-align: right;
}
}
input[type]:not([type="submit"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]) {
background: $white;
}
.control {
cursor: pointer;
margin-bottom: 1rem;
small {
display: block;
margin-top: -1rem;
color: $text-medium;
@include breakpoint(medium) {
margin-left: 0.25rem;
display: inline-block;
margin-top: 0;
}
}
}
.fullscreen-container {
text-align: center;
background: #ccdbe6;
.markdown-editor-header,
.markdown-editor-buttons {
display: none;
}
.fullscreen-container {
a {
line-height: 8rem;
span {
text-decoration: none;
font-size: $small-font-size;
}
.icon-expand {
margin-left: 0.25rem;
vertical-align: sub;
text-decoration: none;
line-height: 0;
}
&:active,
&:focus,
&:hover {
text-decoration: none;
}
}
}
}
#legislation_draft_version_body {
font-family: $font-family-serif;
background: #f5f5f5;
height: 16em;
&:focus {
border: 1px solid #cacaca;
box-shadow: inset 0 1px 2px rgba(34, 34, 34, 0.1);
}
}
#markdown-preview {
font-family: $font-family-serif;
border: 1px solid #cacaca;
margin-bottom: 2rem;
h1, h2, h3, h4, h5, h6 {
font-family: $font-family-serif !important;
font-size: 1rem;
line-height: 1.625rem;
margin-bottom: 0;
}
p {
font-size: 1rem;
line-height: 1.625rem;
}
}
.fullscreen {
.markdown-area,
#markdown-preview {
display: block;
}
.column {
padding: 0;
}
.fullscreen {
.fullscreen-container {
text-align: left;
background: $admin-color;
padding: 0.5rem 1rem;
margin-bottom: 0;
a {
line-height: 3rem;
@@ -865,6 +745,7 @@ table {
float: right;
padding-left: 1rem;
}
}
input {
font-size: $small-font-size;
@@ -877,30 +758,129 @@ table {
margin: 0.5rem;
}
}
}
a {
color: $white;
}
}
}
#legislation_draft_version_body {
.legislation-draft-versions-form {
.legislation-process-version {
@include breakpoint(medium) {
text-align: right;
}
}
[type]:not([type="submit"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]) {
background: $white;
}
.control {
cursor: pointer;
margin-bottom: 1rem;
small {
display: block;
margin-top: -1rem;
color: $text-medium;
@include breakpoint(medium) {
margin-left: 0.25rem;
display: inline-block;
margin-top: 0;
}
}
}
.fullscreen-container {
text-align: center;
background: #ccdbe6;
.markdown-editor-header,
.markdown-editor-buttons {
display: none;
}
span {
text-decoration: none;
font-size: $small-font-size;
}
.icon-expand {
margin-left: 0.25rem;
vertical-align: sub;
text-decoration: none;
line-height: 0;
}
}
.legislation-draft-version-body {
font-family: $font-family-serif;
background: #f5f5f5;
height: 16em;
&:focus {
border: 1px solid #cacaca;
box-shadow: inset 0 1px 2px rgba(34, 34, 34, 0.1);
}
}
.markdown-preview {
font-family: $font-family-serif;
border: 1px solid #cacaca;
margin-bottom: 2rem;
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: $font-family-serif !important;
font-size: 1rem;
line-height: 1.625rem;
margin-bottom: 0;
}
p {
font-size: 1rem;
line-height: 1.625rem;
}
}
.fullscreen {
.markdown-area,
.markdown-preview {
display: block;
}
.column {
padding: 0;
}
.fullscreen-container {
text-align: left;
background: $admin-color;
padding: 0.5rem 1rem;
margin-bottom: 0;
}
.legislation-draft-version-body {
border-radius: 0;
padding: 1rem;
border: none;
border: 0;
@include breakpoint(medium) {
padding: 1rem 2rem;
}
&:focus {
border: none;
}
}
#markdown-preview {
.markdown-preview {
padding: 1rem;
border: none;
border: 0;
@include breakpoint(medium) {
padding: 1rem 2rem;
@@ -908,3 +888,9 @@ table {
}
}
}
.legislation-draft-version-body {
&:focus {
border: 0;
}
}

View File

@@ -2,59 +2,59 @@
//
.annotator-editor .annotator-controls,
.annotator-filter, .annotator-filter
.annotator-filter-navigation button {
background: #F3F3F3;
.annotator-filter,
.annotator-filter .annotator-filter-navigation button {
background: #f3f3f3;
background-image: none;
border: 0;
border-radius: 0;
border-top: 1px solid $border;
box-shadow: none;
padding: $line-height/2 $line-height/4;
padding: $line-height / 2 $line-height / 4;
}
.annotator-adder {
background-image: image-url("annotator_adder.png");
background-image: image-url('annotator_adder.png');
margin-top: -52px;
}
.annotator-widget {
background: white;
background: #fff;
border: 1px solid $border;
border-radius: 0;
bottom: $line-height;
box-shadow: 0 0 5px rgba(0,0,0,0.05);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.05);
font-family: $body-font-family;
font-size: $base-font-size;
line-height: $line-height;
min-width: $line-height*13;
min-width: $line-height * 13;
p {
color: $text;
padding: $line-height/2;
padding: $line-height / 2;
}
}
.annotator-item,
.annotator-editor .annotator-item input:focus,
.annotator-editor .annotator-item textarea:focus {
background: white;
background: #fff;
}
.annotator-widget:after,
.annotator-editor.annotator-invert-y .annotator-widget:after {
background-image: image-url("annotator_items.png");
.annotator-widget::after,
.annotator-editor.annotator-invert-y .annotator-widget::after {
background-image: image-url('annotator_items.png');
}
.annotator-editor a,
.annotator-filter .annotator-filter-property label {
padding: 0 $line-height/4;
padding: 0 $line-height / 4;
}
.annotator-editor a {
background: none;
background-image: none;
border: none;
border: 0;
box-shadow: none;
color: $link;
font-family: $body-font-family;
@@ -63,7 +63,9 @@
text-shadow: none;
padding: 0;
&:hover, &:active, &:focus {
&:hover,
&:active,
&:focus {
background: none;
background-image: none;
color: $link-hover;
@@ -71,28 +73,28 @@
text-shadow: none;
}
&:after {
&::after {
content: none;
}
&.annotator-cancel {
background: #F04124;
color: white;
padding: $line-height/4 $line-height/2;
background: #f04124;
color: #fff;
padding: $line-height / 4 $line-height / 2;
&:hover {
background: darken(#F04124, 20);
background: darken(#f04124, 20);
text-decoration: none;
}
}
&.annotator-save {
background: #43AC6A;
color: white;
padding: $line-height/4 $line-height/2;
background: #43ac6a;
color: #fff;
padding: $line-height / 4 $line-height / 2;
&:hover {
background: darken(#43AC6A, 20);
background: darken(#43ac6a, 20);
text-decoration: none;
}
}
@@ -104,19 +106,23 @@
}
.annotator-hl.weight-1 {
background: #FFF9DA;
background: #fff9da;
}
.annotator-hl.weight-2 {
background: #FFF5BC;
background: #fff5bc;
}
.annotator-hl.weight-3 {
background: #FFF1A2;
background: #fff1a2;
}
.annotator-hl.weight-4 {
background: #FFEF8C;
background: #ffef8c;
}
.annotator-hl.weight-5 {
background: #FFE95F;
background: #ffe95f;
}
.current-annotation {

View File

@@ -1,17 +1,17 @@
@import "social-share-button";
@import "foundation_and_overrides";
@import "fonts";
@import "icons";
@import "mixins";
@import "admin";
@import "layout";
@import "participation";
@import "pages";
@import "legislation";
@import "legislation_process";
@import "custom";
@import "c3";
@import "annotator.min";
@import "annotator_overrides";
@import "jquery-ui/datepicker";
@import "datepicker_overrides";
@import 'social-share-button';
@import 'foundation_and_overrides';
@import 'fonts';
@import 'icons';
@import 'mixins';
@import 'admin';
@import 'layout';
@import 'participation';
@import 'pages';
@import 'legislation';
@import 'legislation_process';
@import 'custom';
@import 'c3';
@import 'annotator.min';
@import 'annotator_overrides';
@import 'jquery-ui/datepicker';
@import 'datepicker_overrides';

View File

@@ -3,7 +3,7 @@
.ui-datepicker-header {
background: $brand;
color: white;
color: #fff;
font-weight: bold;
}
@@ -14,9 +14,17 @@
color: $text;
}
.ui-state-hover, .ui-state-active {
.ui-state-hover,
.ui-state-active {
background: $brand;
color: white;
color: #fff;
}
thead {
tr th {
color: $dark;
}
}
}
@@ -32,8 +40,9 @@
right: 12px;
}
.ui-datepicker-prev, .ui-datepicker-next {
color: white;
.ui-datepicker-prev,
.ui-datepicker-next {
color: #fff;
cursor: pointer;
font-weight: normal;
font-size: $small-font-size;
@@ -44,22 +53,13 @@
table {
border: 1px solid $border;
border-top: 0;
thead {
background: $dark;
border-left: 1px solid $dark;
border-right: 1px solid $dark;
tr th {
color: $dark;
}
}
tr {
border-bottom: 1px solid $border;
&:last-child {
border-bottom: 0px;
border-bottom: 0;
}
&:nth-child(odd) {
@@ -72,23 +72,23 @@
border-right: 1px solid $border;
&:last-child {
border-right: 0px;
border-right: 0;
}
span, a {
span,
a {
text-align: center;
line-height: $line-height;
color: $text;
}
&.ui-datepicker-unselectable.ui-state-disabled {
background: white;
.ui-state-default {
background: #F8F8F8;
color: $text-medium;
}
}
}
}
}
.ui-datepicker-unselectable.ui-state-disabled {
background: #fff;
.ui-state-default {
background: #f8f8f8;
color: $text-medium;
}
}

View File

@@ -16,7 +16,7 @@
font-url('sourcesanspro-light-webfont.woff2') format('woff2'),
font-url('sourcesanspro-light-webfont.woff') format('woff'),
font-url('sourcesanspro-light-webfont.ttf') format('truetype'),
font-url('sourcesanspro-light-webfont.svg#source_sans_prolight') format('svg')
font-url('sourcesanspro-light-webfont.svg#source_sans_prolight') format('svg');
}
@font-face {
@@ -67,7 +67,7 @@
font-url('lato-light.ttf') format('truetype'),
font-url('lato-light.svg#latolight') format('svg');
font-weight: lighter;
font-style: normal
font-style: normal;
}
@font-face {
@@ -79,7 +79,7 @@
font-url('lato-regular.ttf') format('truetype'),
font-url('lato-regular.svg#latoregular') format('svg');
font-weight: normal;
font-style: normal
font-style: normal;
}
@font-face {
@@ -91,5 +91,5 @@
font-url('lato-bold.ttf') format('truetype'),
font-url('lato-bold.svg#latobold') format('svg');
font-weight: bold;
font-style: normal
font-style: normal;
}

View File

@@ -1,4 +1,5 @@
@charset "UTF-8";
@font-face {
font-family: 'icons';
src: font-url('icons.eot');
@@ -9,7 +10,8 @@
font-weight: normal;
font-style: normal;
}
[data-icon]:before {
[data-icon]::before {
font-family: "icons" !important;
content: attr(data-icon);
font-style: normal !important;
@@ -21,8 +23,9 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
[class^="icon-"]:before,
[class*=" icon-"]:before {
[class^="icon-"]::before,
[class*=" icon-"]::before {
font-family: "icons" !important;
font-style: normal !important;
font-weight: normal !important;
@@ -33,168 +36,223 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-angle-down:before {
content: "\61";
.icon-angle-down::before {
content: '\61';
}
.icon-angle-left:before {
content: "\62";
.icon-angle-left::before {
content: '\62';
}
.icon-angle-right:before {
content: "\63";
.icon-angle-right::before {
content: '\63';
}
.icon-angle-up:before {
content: "\64";
.icon-angle-up::before {
content: '\64';
}
.icon-comments:before {
content: "\65";
.icon-comments::before {
content: '\65';
}
.icon-twitter:before {
content: "\66";
.icon-twitter::before {
content: '\66';
}
.icon-calendar:before {
content: "\67";
.icon-calendar::before {
content: '\67';
}
.icon-debates:before {
content: "\69";
.icon-debates::before {
content: '\69';
}
.icon-unlike:before {
content: "\6a";
.icon-unlike::before {
content: '\6a';
}
.icon-like:before {
content: "\6b";
.icon-like::before {
content: '\6b';
}
.icon-check:before {
content: "\6c";
.icon-check::before {
content: '\6c';
}
.icon-edit:before {
content: "\6d";
.icon-edit::before {
content: '\6d';
}
.icon-user:before {
content: "\6f";
.icon-user::before {
content: '\6f';
}
.icon-settings:before {
content: "\71";
.icon-settings::before {
content: '\71';
}
.icon-stats:before {
content: "\72";
.icon-stats::before {
content: '\72';
}
.icon-proposals:before {
content: "\68";
.icon-proposals::before {
content: '\68';
}
.icon-organizations:before {
content: "\73";
.icon-organizations::before {
content: '\73';
}
.icon-deleted:before {
content: "\74";
.icon-deleted::before {
content: '\74';
}
.icon-tag:before {
content: "\75";
.icon-tag::before {
content: '\75';
}
.icon-eye:before {
content: "\70";
.icon-eye::before {
content: '\70';
}
.icon-x:before {
content: "\76";
.icon-x::before {
content: '\76';
}
.icon-flag:before {
content: "\77";
.icon-flag::before {
content: '\77';
}
.icon-comment:before {
content: "\79";
.icon-comment::before {
content: '\79';
}
.icon-reply:before {
content: "\7a";
.icon-reply::before {
content: '\7a';
}
.icon-facebook:before {
content: "\41";
.icon-facebook::before {
content: '\41';
}
.icon-google-plus:before {
content: "\42";
.icon-google-plus::before {
content: '\42';
}
.icon-search:before {
content: "\45";
.icon-search::before {
content: '\45';
}
.icon-external:before {
content: "\46";
.icon-external::before {
content: '\46';
}
.icon-video:before {
content: "\44";
.icon-video::before {
content: '\44';
}
.icon-document:before {
content: "\47";
.icon-document::before {
content: '\47';
}
.icon-print:before {
content: "\48";
.icon-print::before {
content: '\48';
}
.icon-blog:before {
content: "\4a";
.icon-blog::before {
content: '\4a';
}
.icon-box:before {
content: "\49";
.icon-box::before {
content: '\49';
}
.icon-youtube:before {
content: "\4b";
.icon-youtube::before {
content: '\4b';
}
.icon-letter:before {
content: "\4c";
.icon-letter::before {
content: '\4c';
}
.icon-circle:before {
content: "\43";
.icon-circle::before {
content: '\43';
}
.icon-circle-o:before {
content: "\4d";
.icon-circle-o::before {
content: '\4d';
}
.icon-help:before {
content: "\4e";
.icon-help::before {
content: '\4e';
}
.icon-budget:before {
content: "\53";
.icon-budget::before {
content: '\53';
}
.icon-notification:before {
content: "\6e";
.icon-notification::before {
content: '\6e';
}
.icon-no-notification:before {
content: "\78";
.icon-no-notification::before {
content: '\78';
}
.icon-whatsapp:before {
content: "\50";
.icon-whatsapp::before {
content: '\50';
}
.icon-zip:before {
content: "\4f";
.icon-zip::before {
content: '\4f';
}
.icon-banner:before {
content: "\51";
.icon-banner::before {
content: '\51';
}
.icon-arrow-down:before {
content: "\52";
.icon-arrow-down::before {
content: '\52';
}
.icon-arrow-left:before {
content: "\54";
.icon-arrow-left::before {
content: '\54';
}
.icon-arrow-right:before {
content: "\55";
.icon-arrow-right::before {
content: '\55';
}
.icon-check-circle:before {
content: "\56";
.icon-check-circle::before {
content: '\56';
}
.icon-arrow-top:before {
content: "\57";
.icon-arrow-top::before {
content: '\57';
}
.icon-checkmark-circle:before {
content: "\59";
.icon-checkmark-circle::before {
content: '\59';
}
.icon-minus-square:before {
content: "\58";
.icon-minus-square::before {
content: '\58';
}
.icon-plus-square:before {
content: "\5a";
.icon-plus-square::before {
content: '\5a';
}
.icon-expand:before {
content: "\30";
.icon-expand::before {
content: '\30';
}
.icon-telegram:before {
content: "\31";
.icon-telegram::before {
content: '\31';
}
.icon-instagram:before {
content: "\32";
.icon-instagram::before {
content: '\32';
}

View File

@@ -8,7 +8,9 @@
// 01. Global styles
// -----------------
*, *:before, *:after {
*,
*::before,
*::after {
box-sizing: border-box !important;
}
@@ -23,96 +25,344 @@
display: block !important;
}
.column, .columns {
.column,
.columns {
display: inline-block !important;
float: none !important;
box-sizing: border-box !important;
}
.small-1, .row .small-1 { width: 7.33333%; }
.small-2, .row .small-2 { width: 15.66667%; }
.small-3, .row .small-3 { width: 24%; }
.small-4, .row .small-4 { width: 32.33333%; }
.small-5, .row .small-5 { width: 40.66667%; }
.small-6, .row .small-6 { width: 49%; }
.small-7, .row .small-7 { width: 57.33333%; }
.small-8, .row .small-8 { width: 65.66667%; }
.small-9, .row .small-9 { width: 74%; }
.small-10, .row .small-10 { width: 82.33333%; }
.small-11, .row .small-11 { width: 90.66667%; }
.small-12, .row .small-12 { width: 99%; }
.small-1,
.row .small-1 {
width: 7.33333%;
}
.medium-1, .row .medium-1 { width: 7.33333%; }
.medium-2, .row .medium-2 { width: 15.66667%; }
.medium-3, .row .medium-3 { width: 24%; }
.medium-4, .row .medium-4 { width: 32.33333%; }
.medium-5, .row .medium-5 { width: 40.66667%; }
.medium-6, .row .medium-6 { width: 49%; }
.medium-7, .row .medium-7 { width: 57.33333%; }
.medium-8, .row .medium-8 { width: 65.66667%; }
.medium-9, .row .medium-9 { width: 74%; }
.medium-10, .row .medium-10 { width: 82.33333%; }
.medium-11, .row .medium-11 { width: 90.66667%; }
.medium-12, .row .medium-12 { width: 99%; }
.small-2,
.row .small-2 {
width: 15.66667%;
}
.large-1, .row .large-1 { width: 7.33333%; }
.large-2, .row .large-2 { width: 15.66667%; }
.large-3, .row .large-3 { width: 24%; }
.large-4, .row .large-4 { width: 32.33333%; }
.large-5, .row .large-5 { width: 40.66667%; }
.large-6, .row .large-6 { width: 49%; }
.large-7, .row .large-7 { width: 57.33333%; }
.large-8, .row .large-8 { width: 65.66667%; }
.large-9, .row .large-9 { width: 74%; }
.large-10, .row .large-10 { width: 82.33333%; }
.large-11, .row .large-11 { width: 90.66667%; }
.large-12, .row .large-12 { width: 99%; }
.small-3,
.row .small-3 {
width: 24%;
}
.row .small-offset-1 { margin-left: 7.33333%; }
.row .small-offset-2 { margin-left: 15.66667%; }
.row .small-offset-3 { margin-left: 24%; }
.row .small-offset-4 { margin-left: 32.33333%; }
.row .small-offset-5 { margin-left: 40.66667%; }
.row .small-offset-6 { margin-left: 49%; }
.row .small-offset-7 { margin-left: 57.33333%; }
.row .small-offset-8 { margin-left: 65.66667%; }
.row .small-offset-9 { margin-left: 74%; }
.row .small-offset-10 { margin-left: 82.33333%; }
.row .small-offset-11 { margin-left: 90.66667%; }
.row .small-offset-12 { margin-left: 99%; }
.small-4,
.row .small-4 {
width: 32.33333%;
}
.row .medium-offset-1 { margin-left: 7.33333%; }
.row .medium-offset-2 { margin-left: 15.66667%; }
.row .medium-offset-3 { margin-left: 24%; }
.row .medium-offset-4 { margin-left: 32.33333%; }
.row .medium-offset-5 { margin-left: 40.66667%; }
.row .medium-offset-6 { margin-left: 49%; }
.row .medium-offset-7 { margin-left: 57.33333%; }
.row .medium-offset-8 { margin-left: 65.66667%; }
.row .medium-offset-9 { margin-left: 74%; }
.row .medium-offset-10 { margin-left: 82.33333%; }
.row .medium-offset-11 { margin-left: 90.66667%; }
.row .medium-offset-12 { margin-left: 99%; }
.small-5,
.row .small-5 {
width: 40.66667%;
}
.row .large-offset-1 { margin-left: 7.33333%; }
.row .large-offset-2 { margin-left: 15.66667%; }
.row .large-offset-3 { margin-left: 24%; }
.row .large-offset-4 { margin-left: 32.33333%; }
.row .large-offset-5 { margin-left: 40.66667%; }
.row .large-offset-6 { margin-left: 49%; }
.row .large-offset-7 { margin-left: 57.33333%; }
.row .large-offset-8 { margin-left: 65.66667%; }
.row .large-offset-9 { margin-left: 74%; }
.row .large-offset-10 { margin-left: 82.33333%; }
.row .large-offset-11 { margin-left: 90.66667%; }
.row .large-offset-12 { margin-left: 99%; }
.small-6,
.row .small-6 {
width: 49%;
}
.small-7,
.row .small-7 {
width: 57.33333%;
}
.small-8,
.row .small-8 {
width: 65.66667%;
}
.small-9,
.row .small-9 {
width: 74%;
}
.small-10,
.row .small-10 {
width: 82.33333%;
}
.small-11,
.row .small-11 {
width: 90.66667%;
}
.small-12,
.row .small-12 {
width: 99%;
}
.medium-1,
.row .medium-1 {
width: 7.33333%;
}
.medium-2,
.row .medium-2 {
width: 15.66667%;
}
.medium-3,
.row .medium-3 {
width: 24%;
}
.medium-4,
.row .medium-4 {
width: 32.33333%;
}
.medium-5,
.row .medium-5 {
width: 40.66667%;
}
.medium-6,
.row .medium-6 {
width: 49%;
}
.medium-7,
.row .medium-7 {
width: 57.33333%;
}
.medium-8,
.row .medium-8 {
width: 65.66667%;
}
.medium-9,
.row .medium-9 {
width: 74%;
}
.medium-10,
.row .medium-10 {
width: 82.33333%;
}
.medium-11,
.row .medium-11 {
width: 90.66667%;
}
.medium-12,
.row .medium-12 {
width: 99%;
}
.large-1,
.row .large-1 {
width: 7.33333%;
}
.large-2,
.row .large-2 {
width: 15.66667%;
}
.large-3,
.row .large-3 {
width: 24%;
}
.large-4,
.row .large-4 {
width: 32.33333%;
}
.large-5,
.row .large-5 {
width: 40.66667%;
}
.large-6,
.row .large-6 {
width: 49%;
}
.large-7,
.row .large-7 {
width: 57.33333%;
}
.large-8,
.row .large-8 {
width: 65.66667%;
}
.large-9,
.row .large-9 {
width: 74%;
}
.large-10,
.row .large-10 {
width: 82.33333%;
}
.large-11,
.row .large-11 {
width: 90.66667%;
}
.large-12,
.row .large-12 {
width: 99%;
}
.row .small-offset-1 {
margin-left: 7.33333%;
}
.row .small-offset-2 {
margin-left: 15.66667%;
}
.row .small-offset-3 {
margin-left: 24%;
}
.row .small-offset-4 {
margin-left: 32.33333%;
}
.row .small-offset-5 {
margin-left: 40.66667%;
}
.row .small-offset-6 {
margin-left: 49%;
}
.row .small-offset-7 {
margin-left: 57.33333%;
}
.row .small-offset-8 {
margin-left: 65.66667%;
}
.row .small-offset-9 {
margin-left: 74%;
}
.row .small-offset-10 {
margin-left: 82.33333%;
}
.row .small-offset-11 {
margin-left: 90.66667%;
}
.row .small-offset-12 {
margin-left: 99%;
}
.row .medium-offset-1 {
margin-left: 7.33333%;
}
.row .medium-offset-2 {
margin-left: 15.66667%;
}
.row .medium-offset-3 {
margin-left: 24%;
}
.row .medium-offset-4 {
margin-left: 32.33333%;
}
.row .medium-offset-5 {
margin-left: 40.66667%;
}
.row .medium-offset-6 {
margin-left: 49%;
}
.row .medium-offset-7 {
margin-left: 57.33333%;
}
.row .medium-offset-8 {
margin-left: 65.66667%;
}
.row .medium-offset-9 {
margin-left: 74%;
}
.row .medium-offset-10 {
margin-left: 82.33333%;
}
.row .medium-offset-11 {
margin-left: 90.66667%;
}
.row .medium-offset-12 {
margin-left: 99%;
}
.row .large-offset-1 {
margin-left: 7.33333%;
}
.row .large-offset-2 {
margin-left: 15.66667%;
}
.row .large-offset-3 {
margin-left: 24%;
}
.row .large-offset-4 {
margin-left: 32.33333%;
}
.row .large-offset-5 {
margin-left: 40.66667%;
}
.row .large-offset-6 {
margin-left: 49%;
}
.row .large-offset-7 {
margin-left: 57.33333%;
}
.row .large-offset-8 {
margin-left: 65.66667%;
}
.row .large-offset-9 {
margin-left: 74%;
}
.row .large-offset-10 {
margin-left: 82.33333%;
}
.row .large-offset-11 {
margin-left: 90.66667%;
}
.row .large-offset-12 {
margin-left: 99%;
}
.top-bar {
clear: both !important;
height: 100px !important;
}
.locale, .external-links {
.locale,
.external-links {
background: #002d50 !important;
}
@@ -121,11 +371,12 @@
}
.external-links {
color: white !important;
color: #fff !important;
float: right !important;
}
.top-bar-title, .top-bar-title a, .top-bar-title a {
.top-bar-title,
.top-bar-title a {
display: inline-block !important;
float: none !important;
}
@@ -140,28 +391,29 @@
margin: 0 !important;
position: inherit !important;
&:after {
&::after {
content: none !important;
}
}
form {
input, textarea {
input,
textarea {
height: 48px !important;
line-height: 48px !important;
margin-bottom: 24px !important;
width: 100% !important;
}
input[type="checkbox"],
input[type="radio"] {
[type="checkbox"],
[type="radio"] {
height: auto !important;
line-height: inherit !important;
width: auto !important;
}
input[type="radio"] {
[type="radio"] {
width: 18px !important;
}
}
@@ -184,16 +436,17 @@ form {
// 02. Admin
// ---------
body.admin form {
.admin form {
input[type="text"], textarea {
[type="text"],
textarea {
height: 48px !important;
line-height: 48px !important;
margin-bottom: 24px !important;
}
}
.admin-sidebar ul [class^="icon-"] {
.admin-sidebar [class^="icon-"] {
padding-left: 12px !important;
padding-right: 12px !important;
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@
h4 {
font-weight: 400;
text-align: center;
color: white;
color: #fff;
}
}
}
@@ -28,7 +28,7 @@
.legislation-categories {
.menu.simple {
border-bottom: none;
border-bottom: 0;
list-style: none;
padding-left: 0;
margin-left: 0;
@@ -37,6 +37,7 @@
@include breakpoint(medium) {
margin: 1.5rem 0;
}
}
li {
display: block;
@@ -47,9 +48,8 @@
margin-bottom: 2rem;
max-width: 80%;
}
}
li.active {
&.active {
font-weight: 700;
}
}
@@ -58,14 +58,14 @@
// 03. Legislation cards
// -----------------
.legislation {
margin: 0 0 $line-height 0;
background: white;
margin: 0 0 $line-height;
background: #fff;
border: 1px solid;
border-color: #e5e6e9 #dfe0e4 #d0d1d5;
border-radius: 0;
box-shadow: 0px 1px 3px 0 #DEE0E3;
box-shadow: 0 1px 3px 0 #dee0e3;
min-height: 12rem;
padding: 2rem 0 0 0;
padding: 2rem 0 0;
}
.legislation-text {
@@ -83,13 +83,13 @@
}
.legislation-calendar {
background: #E5ECF2;
background: #e5ecf2;
padding-top: 1rem;
h5 {
margin-left: 0.25rem;
margin-bottom: 0;
color: #61686E;
color: #61686e;
@include breakpoint(medium) {
margin-left: 0;

View File

@@ -16,7 +16,7 @@
// -----------------
.grey-heading {
background: #E6E6E6;
background: #e6e6e6;
}
$epigraph-font-size: rem-calc(15);
@@ -39,15 +39,15 @@ $epigraph-line-height: rem-calc(22);
list-style: none;
margin-left: 0;
li:before {
li::before {
vertical-align: text-bottom;
padding-right: 0.5rem;
content: "";
color: #8AA8BE;
content: '';
color: #8aa8be;
}
}
#debate-show {
.legislation-debate-show {
display: none;
}
@@ -82,12 +82,9 @@ $epigraph-line-height: rem-calc(22);
}
.half-gradient {
/* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#e6e6e6+0,e6e6e6+50,ffffff+50 */
background: #e6e6e6; /* Old browsers */
background: -moz-linear-gradient(top, #e6e6e6 0%, #e6e6e6 50%, #ffffff 50%); /* FF3.6-15 */
background: -webkit-linear-gradient(top, #e6e6e6 0%,#e6e6e6 50%,#ffffff 50%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to bottom, #e6e6e6 0%,#e6e6e6 50%,#ffffff 50%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e6e6e6', endColorstr='#ffffff',GradientType=0 ); /* IE6-9 */
background: #e6e6e6;
background: linear-gradient(to bottom, #e6e6e6 0%, #e6e6e6 50%, #fff 50%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e6e6e6', endColorstr='#fff', GradientType=0);
}
.text-center .button {
@@ -115,6 +112,7 @@ $epigraph-line-height: rem-calc(22);
ul {
font-size: $epigraph-font-size;
line-height: $epigraph-line-height;
}
li {
margin-bottom: 1rem;
@@ -124,7 +122,6 @@ $epigraph-line-height: rem-calc(22);
margin-bottom: 0;
}
}
}
h4 {
font-size: $base-font-size;
@@ -134,6 +131,11 @@ $epigraph-line-height: rem-calc(22);
.button-subscribe {
margin-top: 1rem;
@include breakpoint(medium) {
padding: 0.5em 1em;
margin-top: 3rem;
}
h3 {
margin-bottom: 0;
}
@@ -144,12 +146,7 @@ $epigraph-line-height: rem-calc(22);
}
&:hover h3 {
color: white;
}
@include breakpoint(medium) {
padding: 0.5em 1em;
margin-top: 3rem;
color: #fff;
}
}
}
@@ -161,7 +158,7 @@ $epigraph-line-height: rem-calc(22);
.legislation-process-list {
border-bottom: 1px solid $medium-gray;
margin: 0 1rem 1rem 1rem;
margin: 0 1rem 1rem;
padding-top: 4rem;
@include breakpoint(medium) {
@@ -190,6 +187,7 @@ $epigraph-line-height: rem-calc(22);
left: -1rem;
}
}
}
li {
cursor: pointer;
@@ -215,12 +213,13 @@ $epigraph-line-height: rem-calc(22);
a,
h4 {
display: block;
color: #6D6D6D;
color: #6d6d6d;
margin-bottom: 0;
}
a {
&:hover, &:active {
&:hover,
&:active {
text-decoration: none;
}
@@ -238,7 +237,6 @@ $epigraph-line-height: rem-calc(22);
border-bottom: 2px solid $brand;
}
}
}
}
// 04. Debate list
@@ -269,21 +267,21 @@ $epigraph-line-height: rem-calc(22);
.debate-title a {
color: $brand;
}
}
.debate-meta,
.debate-meta a {
font-size: $small-font-size;
color: #6D6D6D;
color: #6d6d6d;
.icon-comments {
margin-right: 0.2rem;
}
}
}
.debate-info {
padding: 1rem;
background: #F4F4F4;
background: #f4f4f4;
}
}
@@ -297,13 +295,14 @@ $epigraph-line-height: rem-calc(22);
.quiz-header {
margin-bottom: 2rem;
.quiz-title, .quiz-next {
.quiz-title,
.quiz-next {
padding: 1rem;
height: 6rem;
}
.quiz-title {
background: #E5ECF2;
background: #e5ecf2;
.quiz-header-title {
margin-bottom: 0;
@@ -324,12 +323,13 @@ $epigraph-line-height: rem-calc(22);
.quiz-next-link {
display: block;
&:hover, &:active {
&:hover,
&:active {
text-decoration: none;
}
.quiz-next {
background: #CCDBE5;
background: #ccdbe5;
font-weight: 700;
color: $brand;
font-size: $small-font-size;
@@ -341,13 +341,14 @@ $epigraph-line-height: rem-calc(22);
vertical-align: sub;
}
&:hover, &:active {
&:hover,
&:active {
text-decoration: none;
background: $brand;
color: white;
color: #fff;
.icon-angle-right {
color: white;
color: #fff;
}
}
}
@@ -380,8 +381,8 @@ $epigraph-line-height: rem-calc(22);
}
.active {
background: #CCDBE6;
border: none;
background: #ccdbe6;
border: 0;
}
.control input {
@@ -392,7 +393,7 @@ $epigraph-line-height: rem-calc(22);
.control input:checked ~ .control-indicator {
background-color: $brand;
border: none;
border: 0;
}
.radio .control-indicator {
@@ -409,11 +410,8 @@ $epigraph-line-height: rem-calc(22);
line-height: 1rem;
font-size: 65%;
text-align: center;
border: 2px solid #9C9C9C;
border: 2px solid #9c9c9c;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
}
@@ -422,9 +420,9 @@ $epigraph-line-height: rem-calc(22);
// 06. Legislation draft
// -----------------
.debate-draft {
padding: 10rem 2rem 15rem 2rem;
padding: 10rem 2rem 15rem;
display: block;
background: #F2F2F2;
background: #f2f2f2;
button {
height: 90px;
@@ -445,7 +443,7 @@ $epigraph-line-height: rem-calc(22);
.legislation-allegation {
padding-top: 1rem;
#debate-show {
.legislation-debate-show {
margin-top: 2rem;
}
@@ -459,7 +457,7 @@ $epigraph-line-height: rem-calc(22);
.button-circle {
line-height: 0;
padding: 0em;
padding: 0;
width: 30px;
height: 30px;
border-radius: 50%;
@@ -467,7 +465,7 @@ $epigraph-line-height: rem-calc(22);
span {
padding-left: 1px;
&:before {
&::before {
line-height: 1.55;
}
}
@@ -482,8 +480,12 @@ $epigraph-line-height: rem-calc(22);
.button-subscribed {
margin-top: 1rem;
border: 1px solid #D1D1D1;
background: #F2F2F2;
border: 1px solid #d1d1d1;
background: #f2f2f2;
@include breakpoint(medium) {
padding: 0.5em 1em;
}
h3 {
display: inline-block;
@@ -499,10 +501,6 @@ $epigraph-line-height: rem-calc(22);
&:hover h3 {
color: $text;
}
@include breakpoint(medium) {
padding: 0.5em 1em;
}
}
}
@@ -547,7 +545,7 @@ $epigraph-line-height: rem-calc(22);
a {
text-decoration: underline;
color: $text-medium
color: $text-medium;
}
}
}
@@ -585,12 +583,12 @@ $epigraph-line-height: rem-calc(22);
}
.calc-text {
width: calc(65% - 25px)
width: calc(65% - 25px);
}
.calc-comments {
cursor: pointer;
background: #F2F2F2;
background: #f2f2f2;
width: 50px;
.draft-panel {
@@ -618,7 +616,7 @@ $epigraph-line-height: rem-calc(22);
font-weight: 700;
padding: 0.5rem 1rem;
color: #696969;
background: #F2F2F2;
background: #f2f2f2;
font-size: $small-font-size;
.icon-comments {
@@ -647,21 +645,23 @@ $epigraph-line-height: rem-calc(22);
li {
margin-bottom: 1rem;
}
.open::before {
cursor: pointer;
position: absolute;
margin-left: -1.25rem;
font-family: "icons";
content: "\58";
font-family: 'icons';
content: '\58';
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.closed::before {
cursor: pointer;
position: absolute;
margin-left: -1.25rem;
font-family: "icons";
content: "\5a";
font-family: 'icons';
content: '\5a';
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@@ -689,7 +689,7 @@ $epigraph-line-height: rem-calc(22);
.anchor::before {
display: none;
content: "#";
content: '#';
color: $text-medium;
position: absolute;
left: 0;
@@ -732,10 +732,7 @@ $epigraph-line-height: rem-calc(22);
font-weight: 700;
color: #696969;
margin-top: 4rem;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
}
}
@@ -746,7 +743,7 @@ $epigraph-line-height: rem-calc(22);
.comments-on {
.calc-index {
width: 50px;
background: #F2F2F2;
background: #f2f2f2;
cursor: pointer;
.panel-title {
@@ -766,10 +763,7 @@ $epigraph-line-height: rem-calc(22);
font-weight: 700;
color: #696969;
margin-top: $line-height;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
.panel-title {
@@ -781,11 +775,11 @@ $epigraph-line-height: rem-calc(22);
.calc-text {
width: calc(65% - 25px);
border-right: none;
border-right: 0;
.show-comments {
width: 105%;
background: #FAFAFA;
background: #fafafa;
padding: 0.25rem 2.5rem 0.25rem 0.25rem;
border: 1px solid $border;
margin-bottom: 1rem;
@@ -798,7 +792,7 @@ $epigraph-line-height: rem-calc(22);
}
.calc-comments {
background: white;
background: #fff;
cursor: auto;
width: calc(35% - 25px);
@@ -810,7 +804,7 @@ $epigraph-line-height: rem-calc(22);
display: none;
}
#comments-box {
.comments-box-container {
position: absolute;
top: 230px;
}
@@ -818,7 +812,7 @@ $epigraph-line-height: rem-calc(22);
.comment-box {
width: 375px;
padding: 1rem;
background: #F9F9F9;
background: #f9f9f9;
border: 1px solid $border;
display: block;
margin-bottom: 2rem;
@@ -868,7 +862,7 @@ $epigraph-line-height: rem-calc(22);
.participation-not-allowed {
font-size: 0.875rem;
height: 50px;
padding: .85rem 0.75rem;
padding: 0.85rem 0.75rem;
top: -18px;
}
}
@@ -880,21 +874,21 @@ $epigraph-line-height: rem-calc(22);
border-bottom: 1px solid $border;
.comment-advice {
border-top: 1px solid #D0D0D0;
border-right: 1px solid #D0D0D0;
border-left: 1px solid #D0D0D0;
border-top: 1px solid #d0d0d0;
border-right: 1px solid #d0d0d0;
border-left: 1px solid #d0d0d0;
width: 100%;
padding: 0.5rem;
display: inline-block;
font-size: $small-font-size;
background: #DFDFDF;
background: #dfdfdf;
.icon-proposals {
color: #838383;
}
a {
color: #87A3B9;
color: #87a3b9;
text-decoration: underline;
}
}
@@ -902,9 +896,9 @@ $epigraph-line-height: rem-calc(22);
textarea {
border-radius: 0;
box-shadow: none;
border-bottom: 1px solid #D0D0D0;
border-right: 1px solid #D0D0D0;
border-left: 1px solid #D0D0D0;
border-bottom: 1px solid #d0d0d0;
border-right: 1px solid #d0d0d0;
border-left: 1px solid #d0d0d0;
width: 100%;
height: 200px;
margin-bottom: 0.5rem;
@@ -912,7 +906,7 @@ $epigraph-line-height: rem-calc(22);
.comment-actions {
.cancel-comment {
color: #87A3B9;
color: #87a3b9;
text-decoration: underline;
font-size: $small-font-size;
display: inline-block;
@@ -948,10 +942,11 @@ $epigraph-line-height: rem-calc(22);
display: inline-block;
&::after {
content: "|";
content: '|';
color: #838383;
}
}
.comment-replies {
display: inline-block;
}
@@ -968,14 +963,14 @@ $epigraph-line-height: rem-calc(22);
&::after {
margin-left: 0.25rem;
content: "|";
content: '|';
}
}
.icon-like,
.icon-unlike {
cursor: pointer;
color: #C7C7C7;
color: #c7c7c7;
&:hover,
&:active,
@@ -993,8 +988,8 @@ $epigraph-line-height: rem-calc(22);
}
.draft-panel {
background: #E5E5E5;
border-left: 1px solid #D4D4D4;
background: #e5e5e5;
border-left: 1px solid #d4d4d4;
.panel-title {
display: inline-block;
@@ -1022,7 +1017,7 @@ $epigraph-line-height: rem-calc(22);
&::before {
margin-right: 0.25rem;
content: ""
content: '';
}
.changes-link {
@@ -1041,7 +1036,7 @@ $epigraph-line-height: rem-calc(22);
.icon-external {
text-decoration: none;
color: #999999;
color: #999;
line-height: 0;
vertical-align: sub;
margin-left: 0.5rem;
@@ -1067,9 +1062,9 @@ $epigraph-line-height: rem-calc(22);
}
.comment-section {
background: #FAFAFA;
background: #fafafa;
padding: 1rem;
border: 1px solid #DEE0E3;
border: 1px solid #dee0e3;
margin-top: 0.25rem;
margin-bottom: 1rem;
}
@@ -1085,7 +1080,7 @@ $epigraph-line-height: rem-calc(22);
.icon-expand,
.icon-comments {
text-decoration: none;
color: #999999;
color: #999;
line-height: 0;
}
@@ -1119,9 +1114,9 @@ $epigraph-line-height: rem-calc(22);
}
.comment-section {
background: #FAFAFA;
background: #fafafa;
padding: 1rem;
border: 1px solid #DEE0E3;
border: 1px solid #dee0e3;
margin-top: 0.25rem;
margin-bottom: 1rem;
}
@@ -1137,7 +1132,7 @@ $epigraph-line-height: rem-calc(22);
.icon-expand,
.icon-comments {
text-decoration: none;
color: #999999;
color: #999;
line-height: 0;
}

View File

@@ -7,14 +7,14 @@
// --------
@mixin logo {
color: white;
color: #fff;
display: inline-block;
font-family: 'Lato' !important;
font-size: rem-calc(24);
font-weight: lighter;
@include breakpoint(medium) {
line-height: $line-height*2;
line-height: $line-height * 2;
margin-top: 0;
}
@@ -24,7 +24,7 @@
@include breakpoint(medium) {
height: 80px;
margin-right: $line-height/2;
margin-right: $line-height / 2;
margin-top: 0;
width: 80px;
}

View File

@@ -16,7 +16,7 @@
padding-top: $line-height;
&.light {
background: #ECF0F1;
background: #ecf0f1;
}
}
@@ -36,7 +36,7 @@
@include breakpoint(medium) {
display: inline-block;
margin-right: $line-height/2;
margin-right: $line-height / 2;
}
}
}
@@ -50,7 +50,7 @@
color: $brand;
}
.additional-info {
.additional-info {
margin-bottom: $line-height;
}
@@ -68,23 +68,23 @@
}
}
ul.features {
.features {
list-style-type: circle;
margin-left: $line-height;
@include breakpoint(medium) {
margin: $line-height 0 $line-height $line-height*2.5;
margin: $line-height 0 $line-height $line-height * 2.5;
}
li {
margin-bottom: $line-height
margin-bottom: $line-height;
}
}
.section-content {
border-top: 1px solid $medium-gray;
padding-bottom: $line-height*2;
padding-top: $line-height*2;
padding-bottom: $line-height * 2;
padding-top: $line-height * 2;
&:first-child {
border-top: 0;
@@ -101,10 +101,10 @@
.sidebar-card {
border: 1px solid $border;
margin-bottom: $line-height;
padding: $line-height/2;
padding: $line-height / 2;
&.light {
background: #ECF0F1;
background: #ecf0f1;
border: 0;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
class GraphqlController < ApplicationController
skip_before_action :verify_authenticity_token
skip_authorization_check
class QueryStringError < StandardError; end
def query
begin
if query_string.nil? then raise GraphqlController::QueryStringError end
response = consul_schema.execute query_string, variables: query_variables
render json: response, status: :ok
rescue GraphqlController::QueryStringError
render json: { message: 'Query string not present' }, status: :bad_request
rescue JSON::ParserError
render json: { message: 'Error parsing JSON' }, status: :bad_request
rescue GraphQL::ParseError
render json: { message: 'Query string is not valid JSON' }, status: :bad_request
rescue
unless Rails.env.production? then raise end
end
end
private
def consul_schema
api_types = GraphQL::ApiTypesCreator.create(API_TYPE_DEFINITIONS)
query_type = GraphQL::QueryTypeCreator.create(api_types)
GraphQL::Schema.define do
query query_type
max_depth 8
max_complexity 2500
end
end
def query_string
if request.headers["CONTENT_TYPE"] == 'application/graphql'
request.body.string # request.body.class => StringIO
else
params[:query]
end
end
def query_variables
if params[:variables].blank? || params[:variables] == 'null'
{}
else
JSON.parse(params[:variables])
end
end
end

View File

@@ -1,5 +1,7 @@
class Comment < ActiveRecord::Base
include Flaggable
include HasPublicAuthor
include Graphqlable
acts_as_paranoid column: :hidden_at
include ActsAsParanoidAliases
@@ -24,6 +26,12 @@ class Comment < ActiveRecord::Base
scope :with_visible_author, -> { joins(:user).where("users.hidden_at IS NULL") }
scope :not_as_admin_or_moderator, -> { where("administrator_id IS NULL").where("moderator_id IS NULL")}
scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
scope :public_for_api, -> do
where(%{(comments.commentable_type = 'Debate' and comments.commentable_id in (?)) or
(comments.commentable_type = 'Proposal' and comments.commentable_id in (?))},
Debate.public_for_api.pluck(:id),
Proposal.public_for_api.pluck(:id))
end
scope :sort_by_most_voted, -> { order(confidence_score: :desc, created_at: :desc) }
scope :sort_descendants_by_most_voted, -> { order(confidence_score: :desc, created_at: :asc) }

View File

@@ -0,0 +1,36 @@
module Graphqlable
extend ActiveSupport::Concern
class_methods do
def graphql_field_name
self.name.gsub('::', '_').underscore.to_sym
end
def graphql_field_description
"Find one #{self.model_name.human} by ID"
end
def graphql_pluralized_field_name
self.name.gsub('::', '_').underscore.pluralize.to_sym
end
def graphql_pluralized_field_description
"Find all #{self.model_name.human.pluralize}"
end
def graphql_type_name
self.name.gsub('::', '_')
end
def graphql_type_description
"#{self.model_name.human}"
end
end
def public_created_at
self.created_at.change(min: 0)
end
end

View File

@@ -0,0 +1,5 @@
module HasPublicAuthor
def public_author
self.author.public_activity? ? self.author : nil
end
end

View File

@@ -4,11 +4,11 @@ module Measurable
class_methods do
def title_max_length
@@title_max_length ||= self.columns.find { |c| c.name == 'title' }.limit || 80
@@title_max_length ||= (self.columns.find { |c| c.name == 'title' }.limit rescue nil) || 80
end
def responsible_name_max_length
@@responsible_name_max_length ||= self.columns.find { |c| c.name == 'responsible_name' }.limit || 60
@@responsible_name_max_length ||= (self.columns.find { |c| c.name == 'responsible_name' }.limit rescue nil) || 60
end
def question_max_length

View File

@@ -7,6 +7,8 @@ class Debate < ActiveRecord::Base
include Sanitizable
include Searchable
include Filterable
include HasPublicAuthor
include Graphqlable
acts_as_votable
acts_as_paranoid column: :hidden_at
@@ -37,6 +39,7 @@ class Debate < ActiveRecord::Base
scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
scope :last_week, -> { where("created_at >= ?", 7.days.ago)}
scope :featured, -> { where("featured_at is not null")}
scope :public_for_api, -> { all }
# Ahoy setup
visitable # Ahoy will automatically assign visit_id on create

View File

@@ -1,10 +1,15 @@
class Geozone < ActiveRecord::Base
include Graphqlable
has_many :proposals
has_many :spending_proposals
has_many :debates
has_many :users
validates :name, presence: true
scope :public_for_api, -> { all }
def self.names
Geozone.pluck(:name)
end

View File

@@ -1,4 +1,7 @@
class Organization < ActiveRecord::Base
include Graphqlable
belongs_to :user, touch: true
validates :name, presence: true

View File

@@ -6,6 +6,8 @@ class Proposal < ActiveRecord::Base
include Sanitizable
include Searchable
include Filterable
include HasPublicAuthor
include Graphqlable
acts_as_votable
acts_as_paranoid column: :hidden_at
@@ -51,6 +53,7 @@ class Proposal < ActiveRecord::Base
scope :retired, -> { where.not(retired_at: nil) }
scope :not_retired, -> { where(retired_at: nil) }
scope :successful, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success) }
scope :public_for_api, -> { all }
def to_param
"#{id}-#{title}".parameterize

View File

@@ -1,4 +1,7 @@
class ProposalNotification < ActiveRecord::Base
include Graphqlable
belongs_to :author, class_name: 'User', foreign_key: 'author_id'
belongs_to :proposal
@@ -7,6 +10,8 @@ class ProposalNotification < ActiveRecord::Base
validates :proposal, presence: true
validate :minimum_interval
scope :public_for_api, -> { where(proposal_id: Proposal.public_for_api.pluck(:id)) }
def minimum_interval
return true if proposal.try(:notifications).blank?
if proposal.notifications.last.created_at > (Time.current - Setting[:proposal_notification_minimum_interval_in_days].to_i.days).to_datetime

View File

@@ -10,6 +10,8 @@ class User < ActiveRecord::Base
acts_as_paranoid column: :hidden_at
include ActsAsParanoidAliases
include Graphqlable
has_one :administrator
has_one :moderator
has_one :valuator
@@ -61,6 +63,7 @@ class User < ActiveRecord::Base
scope :email_digest, -> { where(email_digest: true) }
scope :active, -> { where(erased_at: nil) }
scope :erased, -> { where.not(erased_at: nil) }
scope :public_for_api, -> { all }
before_validation :clean_document_number
@@ -288,6 +291,18 @@ class User < ActiveRecord::Base
end
delegate :can?, :cannot?, to: :ability
def public_proposals
public_activity? ? proposals : User.none
end
def public_debates
public_activity? ? debates : User.none
end
def public_comments
public_activity? ? comments : User.none
end
# overwritting of Devise method to allow login using email OR username
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup

View File

@@ -1,2 +1,14 @@
class Vote < ActsAsVotable::Vote
include Graphqlable
scope :public_for_api, -> do
where(%{(votes.votable_type = 'Debate' and votes.votable_id in (?)) or
(votes.votable_type = 'Proposal' and votes.votable_id in (?)) or
(votes.votable_type = 'Comment' and votes.votable_id in (?))},
Debate.public_for_api.pluck(:id),
Proposal.public_for_api.pluck(:id),
Comment.public_for_api.pluck(:id))
end
end

View File

@@ -79,7 +79,7 @@
<div class="small-12 medium-6 column markdown-area">
<%= f.text_area :body, label: false, placeholder: t('admin.legislation.draft_versions.form.body_placeholder') %>
</div>
<div id="markdown-preview" class="small-12 medium-6 column">
<div id="markdown-preview" class="small-12 medium-6 column markdown-preview">
</div>
</div>
</div>

View File

@@ -6,7 +6,7 @@
<h2 class="inline-block"><%= t("admin.site_customization.content_blocks.index.title") %></h2>
<table class="cms_page_list">
<table class="cms-page-list">
<thead>
<tr>
<th><%= t("admin.site_customization.content_blocks.content_block.name") %></th>

View File

@@ -8,7 +8,7 @@
<% if @pages.any? %>
<h3><%= page_entries_info @pages %></h3>
<table class="cms_page_list">
<table class="cms-page-list">
<thead>
<tr>
<th><%= t("admin.site_customization.pages.page.title") %></th>

View File

@@ -1,4 +1,4 @@
<header>
<header class="header">
<div class="top-links">
<div class="expanded row">
<%= render 'shared/locale_switcher' %>

View File

@@ -9,7 +9,7 @@
<span class="panel-title"><%= t('legislation.draft_versions.show.text_comments') %></span>
</div>
<div id="comments-box" style="display: none;">
<div id="comments-box" class="comments-box-container" style="display: none;">
<div class="comment-box">
<div class="comment-header">
<span class="icon-comment" aria-hidden="true"></span>

View File

@@ -6,7 +6,7 @@
</div>
</div>
<div id="debate-show" class="row description">
<div id="debate-show" class="row description legislation-debate-show">
<div class="small-12 column">
<% if process.description.present? %>
<h4><%= t('legislation.processes.header_full.description') %></h4>

View File

@@ -16,7 +16,7 @@
<% end %>
</div>
<% if process.additional_info.present? %>
<div id="debate-show" class="small-12 column debate-add-info">
<div id="debate-show" class="small-12 column debate-add-info legislation-debate-show">
<div class="debate-info-wrapper">
<%= markdown process.additional_info if process.additional_info %>
</div>

View File

@@ -2,7 +2,7 @@
<%= render 'admin/shared/proposal_search', url: management_proposals_path %>
<div class="row">
<div id="proposals" class="proposals-list small-12 medium-9 column">
<div class="proposals-list small-12 column">
<div class="filters">
<div class="small-12 medium-7 float-left">

View File

@@ -1,6 +1,6 @@
<main>
<div class="row">
<div id="proposals" class="proposals-list small-12 column">
<div class="proposals-list small-12 column">
<a id="print_link" href="javascript:window.print();" class="button warning float-right">
<%= t('management.proposals.print.print_button') %>
</a>

View File

@@ -1,7 +1,7 @@
<a href="#" class="button float-right margin-right">Crear nueva página</a>
<h2 class="inline-block">Editar páginas</h2>
<table class="cms_page_list">
<table class="cms-page-list">
<thead>
<tr>
<th>Nombre</th>

View File

@@ -36,7 +36,7 @@
</div>
</div>
<form class="edit_legislation_draft_version" id="edit_legislation_draft_version_1" action="/admin/legislation/processes/1/draft_versions/1" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="_method" value="patch" /><input type="hidden" name="authenticity_token" value="PGlvCYHqh/eXoeE0qz7DEZHsTghiKHY9HSZa2JNj+5pFPxPkF1Zuq3gaAuTmTjjhNi86OYIebJPVjFoaBIRaCA==" />
<form class="edit-legislation-draft-version" id="edit_legislation_draft_version_1" action="/admin/legislation/processes/1/draft_versions/1" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="_method" value="patch" /><input type="hidden" name="authenticity_token" value="PGlvCYHqh/eXoeE0qz7DEZHsTghiKHY9HSZa2JNj+5pFPxPkF1Zuq3gaAuTmTjjhNi86OYIebJPVjFoaBIRaCA==" />
<div class="row">
<div class="small-12 medium-4 column">
@@ -105,7 +105,7 @@ Esta es la primera versión, no hay cambios.</textarea>
<a class="fullscreen-toggle" href="#"><span>Pantalla completa</span> <span class="icon-expand"></span></a>
</div>
<div class="small-12 medium-6 column">
<textarea name="legislation_draft_version[body]" id="legislation_draft_version_body">
<textarea name="legislation_draft_version[body]" id="legislation_draft_version_body" class="legislation-draft-version-body">
## PREÁMBULO
### I
@@ -579,7 +579,7 @@ e) La afectación legal de los terrenos obtenidos por la Administración en virt
3. Antes de la finalización de las obras de urbanización no es posible, con carácter general, la realización de otros actos edificatorios o de implantación de usos que los provisionales previstos en la letra b) del número 1 anterior. Sin embargo, podrá autorizarse la edificación vinculada a la simultánea terminación de las obras de urbanización inmediata a la parcela de que se trate, en las mismas condiciones que en el suelo urbano consolidado.
</textarea>
</div>
<div id="markdown-preview" class="small-12 medium-6 column"></div>
<div id="markdown-preview" class="small-12 medium-6 column markdown-preview"></div>
<div id="markdown-result">
<input label="false" type="hidden" value="&lt;h2&gt;PREÁMBULO&lt;/h2&gt;
&lt;h3&gt;I&lt;/h3&gt;

View File

@@ -18,7 +18,7 @@
</li>
</ul>
<form class="edit_legislation_process" id="edit_legislation_process_1" action="/admin/legislation/processes/1" accept-charset="UTF-8" method="post">
<form class="edit-legislation-process" id="edit_legislation_process_1" action="/admin/legislation/processes/1" accept-charset="UTF-8" method="post">
<div class="row">
<div class="small-12 medium-4 column">

89
config/api.yml Normal file
View File

@@ -0,0 +1,89 @@
User:
fields:
id: integer
username: string
public_debates: [Debate]
public_proposals: [Proposal]
public_comments: [Comment]
# organization: Organization
Debate:
fields:
id: integer
title: string
description: string
public_created_at: string
cached_votes_total: integer
cached_votes_up: integer
cached_votes_down: integer
comments_count: integer
hot_score: integer
confidence_score: integer
comments: [Comment]
public_author: User
votes_for: [Vote]
tags: ["ActsAsTaggableOn::Tag"]
Proposal:
fields:
id: integer
title: string
description: string
external_url: string
cached_votes_up: integer
comments_count: integer
hot_score: integer
confidence_score: integer
public_created_at: string
summary: string
video_url: string
geozone_id: integer
retired_at: string
retired_reason: string
retired_explanation: string
geozone: Geozone
comments: [Comment]
proposal_notifications: [ProposalNotification]
public_author: User
votes_for: [Vote]
tags: ["ActsAsTaggableOn::Tag"]
Comment:
fields:
id: integer
commentable_id: integer
commentable_type: string
body: string
public_created_at: string
cached_votes_total: integer
cached_votes_up: integer
cached_votes_down: integer
ancestry: string
confidence_score: integer
public_author: User
votes_for: [Vote]
Geozone:
fields:
id: integer
name: string
ProposalNotification:
fields:
title: string
body: string
proposal_id: integer
public_created_at: string
proposal: Proposal
ActsAsTaggableOn::Tag:
fields:
id: integer
name: string
taggings_count: integer
kind: string
Vote:
fields:
votable_id: integer
votable_type: string
public_created_at: string
vote_flag: boolean
# Organization:
# fields:
# id: integer
# user_id: integer
# name: string

View File

@@ -5,6 +5,15 @@ module ActsAsTaggableOn
after_create :increment_tag_custom_counter
after_destroy :touch_taggable, :decrement_tag_custom_counter
scope :public_for_api, -> do
where(%{taggings.tag_id in (?) and
(taggings.taggable_type = 'Debate' and taggings.taggable_id in (?)) or
(taggings.taggable_type = 'Proposal' and taggings.taggable_id in (?))},
Tag.where('kind IS NULL or kind = ?', 'category').pluck(:id),
Debate.public_for_api.pluck(:id),
Proposal.public_for_api.pluck(:id))
end
def touch_taggable
taggable.touch if taggable.present?
end
@@ -20,6 +29,14 @@ module ActsAsTaggableOn
Tag.class_eval do
include Graphqlable
scope :public_for_api, -> do
where('(tags.kind IS NULL or tags.kind = ?) and tags.id in (?)',
'category',
Tagging.public_for_api.pluck('DISTINCT taggings.tag_id'))
end
def increment_custom_counter_for(taggable_type)
Tag.increment_counter(custom_counter_field_name_for(taggable_type), id)
end
@@ -42,6 +59,18 @@ module ActsAsTaggableOn
ActsAsTaggableOn::Tag.where('taggings.taggable_type' => 'SpendingProposal').includes(:taggings).order(:name).uniq
end
def self.graphql_field_name
:tag
end
def self.graphql_pluralized_field_name
:tags
end
def self.graphql_type_name
'Tag'
end
private
def custom_counter_field_name_for(taggable_type)
"#{taggable_type.underscore.pluralize}_count"

View File

@@ -0,0 +1,4 @@
if ActiveRecord::Base.connection.tables.any?
api_config = YAML.load_file('./config/api.yml')
API_TYPE_DEFINITIONS = GraphQL::ApiTypesCreator::parse_api_config_file(api_config)
end

View File

@@ -400,10 +400,15 @@ Rails.application.routes.draw do
root to: "dashboard#index"
end
# GraphQL
get '/graphql', to: 'graphql#query'
post '/graphql', to: 'graphql#query'
if Rails.env.development?
mount LetterOpenerWeb::Engine, at: "/letter_opener"
end
mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql'
mount Tolk::Engine => '/translate', :as => 'tolk'
# more info pages

View File

@@ -59,7 +59,17 @@ print "Creating Users"
def create_user(email, username = Faker::Name.name)
pwd = '12345678'
User.create!(username: username, email: email, password: pwd, password_confirmation: pwd, confirmed_at: Time.current, terms_of_service: "1")
User.create!(
username: username,
email: email,
password: pwd,
password_confirmation: pwd,
confirmed_at: Time.current,
terms_of_service: "1",
gender: ['Male', 'Female'].sample,
date_of_birth: rand((Time.current - 80.years) .. (Time.current - 16.years)),
public_activity: (rand(1..100) > 30)
)
end
admin = create_user('admin@consul.dev', 'admin')
@@ -103,11 +113,11 @@ end
official.update(official_level: i, official_position: "Official position #{i}")
end
(1..40).each do |i|
(1..100).each do |i|
user = create_user("user#{i}@consul.dev")
level = [1, 2, 3].sample
if level >= 2
user.update(residence_verified_at: Time.current, confirmed_phone: Faker::PhoneNumber.phone_number, document_number: Faker::Number.number(10), document_type: "1")
user.update(residence_verified_at: Time.current, confirmed_phone: Faker::PhoneNumber.phone_number, document_number: Faker::Number.number(10), document_type: "1", geozone: Geozone.reorder("RANDOM()").first)
end
if level == 3
user.update(verified_at: Time.current, document_number: Faker::Number.number(10))
@@ -285,7 +295,7 @@ puts " ✅"
print "Voting Debates, Proposals & Comments"
100.times do
voter = not_org_users.reorder("RANDOM()").first
voter = not_org_users.level_two_or_three_verified.reorder("RANDOM()").first
vote = [true, false].sample
debate = Debate.reorder("RANDOM()").first
debate.vote_by(voter: voter, vote: vote)
@@ -299,7 +309,7 @@ end
end
100.times do
voter = User.level_two_or_three_verified.reorder("RANDOM()").first
voter = not_org_users.level_two_or_three_verified.reorder("RANDOM()").first
proposal = Proposal.reorder("RANDOM()").first
proposal.vote_by(voter: voter, vote: true)
end
@@ -487,6 +497,16 @@ Proposal.last(3).each do |proposal|
created_at: rand((Time.current - 1.week)..Time.current))
end
puts ""
puts "Creating proposal notifications"
100.times do |i|
ProposalNotification.create!(title: "Proposal notification title #{i}",
body: "Proposal notification body #{i}",
author: User.reorder("RANDOM()").first,
proposal: Proposal.reorder("RANDOM()").first)
end
puts ""
print "Creating polls"

View File

@@ -232,10 +232,10 @@ ActiveRecord::Schema.define(version: 20170613203256) do
t.string "visit_id"
t.datetime "hidden_at"
t.integer "flags_count", default: 0
t.datetime "ignored_flag_at"
t.integer "cached_votes_total", default: 0
t.integer "cached_votes_up", default: 0
t.integer "cached_votes_down", default: 0
t.datetime "ignored_flag_at"
t.integer "comments_count", default: 0
t.datetime "confirmed_hide_at"
t.integer "cached_anonymous_votes_total", default: 0
@@ -254,7 +254,6 @@ ActiveRecord::Schema.define(version: 20170613203256) do
add_index "debates", ["cached_votes_total"], name: "index_debates_on_cached_votes_total", using: :btree
add_index "debates", ["cached_votes_up"], name: "index_debates_on_cached_votes_up", using: :btree
add_index "debates", ["confidence_score"], name: "index_debates_on_confidence_score", using: :btree
add_index "debates", ["description"], name: "index_debates_on_description", using: :btree
add_index "debates", ["geozone_id"], name: "index_debates_on_geozone_id", using: :btree
add_index "debates", ["hidden_at"], name: "index_debates_on_hidden_at", using: :btree
add_index "debates", ["hot_score"], name: "index_debates_on_hot_score", using: :btree
@@ -718,7 +717,6 @@ ActiveRecord::Schema.define(version: 20170613203256) do
add_index "proposals", ["author_id"], name: "index_proposals_on_author_id", using: :btree
add_index "proposals", ["cached_votes_up"], name: "index_proposals_on_cached_votes_up", using: :btree
add_index "proposals", ["confidence_score"], name: "index_proposals_on_confidence_score", using: :btree
add_index "proposals", ["description"], name: "index_proposals_on_description", using: :btree
add_index "proposals", ["geozone_id"], name: "index_proposals_on_geozone_id", using: :btree
add_index "proposals", ["hidden_at"], name: "index_proposals_on_hidden_at", using: :btree
add_index "proposals", ["hot_score"], name: "index_proposals_on_hot_score", using: :btree
@@ -926,7 +924,7 @@ ActiveRecord::Schema.define(version: 20170613203256) do
t.boolean "email_digest", default: true
t.boolean "email_on_direct_message", default: true
t.boolean "official_position_badge", default: false
t.datetime "password_changed_at", default: '2016-11-23 10:59:20', null: false
t.datetime "password_changed_at", default: '2016-12-21 17:55:08', null: false
t.boolean "created_from_signature", default: false
t.integer "failed_email_digests_count", default: 0
t.text "former_users_data_log", default: ""

View File

@@ -0,0 +1,84 @@
require 'graphql'
module GraphQL
class ApiTypesCreator
SCALAR_TYPES = {
integer: GraphQL::INT_TYPE,
boolean: GraphQL::BOOLEAN_TYPE,
float: GraphQL::FLOAT_TYPE,
double: GraphQL::FLOAT_TYPE,
string: GraphQL::STRING_TYPE
}
def self.create(api_types_definitions)
created_types = {}
api_types_definitions.each do |model, info|
create_type(model, info[:fields], created_types)
end
created_types
end
def self.type_kind(type)
if SCALAR_TYPES[type]
:scalar
elsif type.class == Class
:singular_association
elsif type.class == Array
:multiple_association
end
end
def self.create_type(model, fields, created_types)
created_types[model] = GraphQL::ObjectType.define do
name model.graphql_type_name
description model.graphql_type_description
# Make a field for each column, association or method
fields.each do |field_name, field_type|
case ApiTypesCreator.type_kind(field_type)
when :scalar
field(field_name, SCALAR_TYPES[field_type], model.human_attribute_name(field_name))
when :singular_association
field(field_name, -> { created_types[field_type] }) do
resolve -> (object, arguments, context) do
association_target = object.send(field_name)
association_target.present? ? field_type.public_for_api.find_by(id: association_target.id) : nil
end
end
when :multiple_association
field_type = field_type.first
connection(field_name, -> { created_types[field_type].connection_type }, max_page_size: 50, complexity: 1000) do
resolve -> (object, arguments, context) { object.send(field_name).public_for_api }
end
end
end
end
end
def self.parse_api_config_file(file)
api_type_definitions = {}
file.each do |api_type_model, api_type_info|
model = api_type_model.constantize
fields = {}
api_type_info['fields'].each do |field_name, field_type|
if field_type.is_a?(Array) # paginated association
fields[field_name.to_sym] = [field_type.first.constantize]
elsif SCALAR_TYPES[field_type.to_sym]
fields[field_name.to_sym] = field_type.to_sym
else # simple association
fields[field_name.to_sym] = field_type.constantize
end
end
api_type_definitions[model] = { fields: fields }
end
api_type_definitions
end
end
end

View File

@@ -0,0 +1,31 @@
require 'graphql'
module GraphQL
class QueryTypeCreator
def self.create(api_types)
GraphQL::ObjectType.define do
name 'QueryType'
description 'The root query for the schema'
api_types.each do |model, created_type|
if created_type.fields['id']
field model.graphql_field_name do
type created_type
description model.graphql_field_description
argument :id, !types.ID
resolve -> (object, arguments, context) { model.public_for_api.find_by(id: arguments['id'])}
end
end
connection(model.graphql_pluralized_field_name, created_type.connection_type, max_page_size: 50, complexity: 1000) do
description model.graphql_pluralized_field_description
resolve -> (object, arguments, context) { model.public_for_api }
end
end
end
end
end
end

View File

@@ -0,0 +1,91 @@
require 'rails_helper'
# Useful resource: http://graphql.org/learn/serving-over-http/
def parser_error_raised?(response)
data_is_empty = response['data'].nil?
error_is_present = (JSON.parse(response.body)['errors'].first['message'] =~ /^Parse error on/)
data_is_empty && error_is_present
end
describe GraphqlController, type: :request do
let(:proposal) { create(:proposal) }
describe "handles GET request" do
specify "with query string inside query params" do
get '/graphql', query: "{ proposal(id: #{proposal.id}) { title } }"
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)['data']['proposal']['title']).to eq(proposal.title)
end
specify "with malformed query string" do
get '/graphql', query: 'Malformed query string'
expect(response).to have_http_status(:ok)
expect(parser_error_raised?(response)).to be_truthy
end
specify "without query string" do
get '/graphql'
expect(response).to have_http_status(:bad_request)
expect(JSON.parse(response.body)['message']).to eq('Query string not present')
end
end
describe "handles POST request" do
let(:json_headers) { { "CONTENT_TYPE" => "application/json" } }
specify "with json-encoded query string inside body" do
post '/graphql', { query: "{ proposal(id: #{proposal.id}) { title } }" }.to_json, json_headers
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)['data']['proposal']['title']).to eq(proposal.title)
end
specify "with raw query string inside body" do
graphql_headers = { "CONTENT_TYPE" => "application/graphql" }
post '/graphql', "{ proposal(id: #{proposal.id}) { title } }", graphql_headers
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)['data']['proposal']['title']).to eq(proposal.title)
end
specify "with malformed query string" do
post '/graphql', { query: "Malformed query string" }.to_json, json_headers
expect(response).to have_http_status(:ok)
expect(parser_error_raised?(response)).to be_truthy
end
it "without query string" do
post '/graphql', json_headers
expect(response).to have_http_status(:bad_request)
expect(JSON.parse(response.body)['message']).to eq('Query string not present')
end
end
describe "correctly parses query variables" do
let(:query_string) { "{ proposal(id: #{proposal.id}) { title } }" }
specify "when absent" do
get '/graphql', { query: query_string }
expect(response).to have_http_status(:ok)
end
specify "when specified as the 'null' string" do
get '/graphql', { query: query_string, variables: 'null' }
expect(response).to have_http_status(:ok)
end
specify "when specified as an empty string" do
get '/graphql', { query: query_string, variables: '' }
expect(response).to have_http_status(:ok)
end
end
end

View File

@@ -8,6 +8,7 @@ FactoryGirl.define do
password 'judgmentday'
terms_of_service '1'
confirmed_at { Time.current }
public_activity true
trait :incomplete_verification do
after :create do |user|

View File

@@ -86,15 +86,18 @@ feature 'Admin legislation draft versions' do
click_link 'Version 1'
fill_in 'legislation_draft_version_title', with: 'Version 1b'
click_link 'Launch text editor'
fill_in 'legislation_draft_version_title', with: 'Version 1b'
fill_in 'legislation_draft_version_body', with: '# Version 1 body\r\n\r\nParagraph\r\n\r\n>Quote'
within('.fullscreen') do
click_button 'Save changes'
click_link 'Close text editor'
end
click_button 'Save changes'
expect(page).to have_content 'Version 1b'
end
end

View File

@@ -97,7 +97,7 @@ feature 'Proposals' do
expect(current_path).to eq(management_proposals_path)
within("#proposals") do
within(".proposals-list") do
expect(page).to have_css('.proposal', count: 1)
expect(page).to have_content(proposal1.title)
expect(page).to have_content(proposal1.summary)
@@ -124,7 +124,7 @@ feature 'Proposals' do
expect(page).to have_content "#{user.document_number}"
end
within("#proposals") do
within(".proposals-list") do
expect(page).to have_css('.proposal', count: 2)
expect(page).to have_css("a[href='#{management_proposal_path(proposal1)}']", text: proposal1.title)
expect(page).to have_content(proposal1.summary)
@@ -143,7 +143,7 @@ feature 'Proposals' do
click_link "Support proposals"
within("#proposals") do
within(".proposals-list") do
find('.in-favor a').click
expect(page).to have_content "1 support"
@@ -160,7 +160,7 @@ feature 'Proposals' do
click_link "Support proposals"
within("#proposals") do
within(".proposals-list") do
click_link proposal.title
end
@@ -205,7 +205,7 @@ feature 'Proposals' do
expect(page).to have_selector('.js-order-selector[data-order="confidence_score"]')
within '#proposals' do
within(".proposals-list") do
expect('Best proposal').to appear_before('Medium proposal')
expect('Medium proposal').to appear_before('Worst proposal')
end
@@ -217,7 +217,7 @@ feature 'Proposals' do
expect(current_url).to include('order=created_at')
expect(current_url).to include('page=1')
within '#proposals' do
within(".proposals-list") do
expect('Medium proposal').to appear_before('Best proposal')
expect('Best proposal').to appear_before('Worst proposal')
end

View File

@@ -64,6 +64,75 @@ describe 'ActsAsTaggableOn' do
expect(tag.proposals_count).to eq(1)
end
end
describe "public_for_api scope" do
it "returns tags whose kind is NULL and have at least one tagging whose taggable is not hidden" do
tag = create(:tag, kind: nil)
proposal = create(:proposal)
proposal.tag_list.add(tag)
proposal.save
expect(ActsAsTaggableOn::Tag.public_for_api).to include(tag)
end
it "returns tags whose kind is 'category' and have at least one tagging whose taggable is not hidden" do
tag = create(:tag, kind: 'category')
proposal = create(:proposal)
proposal.tag_list.add(tag)
proposal.save
expect(ActsAsTaggableOn::Tag.public_for_api).to include(tag)
end
it "blocks other kinds of tags" do
tag = create(:tag, kind: 'foo')
proposal = create(:proposal)
proposal.tag_list.add(tag)
proposal.save
expect(ActsAsTaggableOn::Tag.public_for_api).not_to include(tag)
end
it "blocks tags that don't have at least one tagged element" do
tag = create(:tag)
expect(ActsAsTaggableOn::Tag.public_for_api).to_not include(tag)
end
it 'only permits tags on proposals or debates' do
tag_1 = create(:tag)
tag_2 = create(:tag)
tag_3 = create(:tag)
proposal = create(:proposal)
spending_proposal = create(:spending_proposal)
debate = create(:debate)
proposal.tag_list.add(tag_1)
spending_proposal.tag_list.add(tag_2)
debate.tag_list.add(tag_3)
proposal.save
spending_proposal.save
debate.save
expect(ActsAsTaggableOn::Tag.public_for_api).to match_array([tag_1, tag_3])
end
it 'blocks tags after its taggings became hidden' do
tag = create(:tag)
proposal = create(:proposal)
proposal.tag_list.add(tag)
proposal.save
expect(ActsAsTaggableOn::Tag.public_for_api).to include(tag)
proposal.delete
expect(ActsAsTaggableOn::Tag.public_for_api).to be_empty
end
end
end
end

View File

@@ -0,0 +1,58 @@
require 'rails_helper'
describe GraphQL::ApiTypesCreator do
let(:created_types) { {} }
describe "::create_type" do
it "creates fields for Int attributes" do
debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { id: :integer }, created_types)
created_field = debate_type.fields['id']
expect(created_field).to be_a(GraphQL::Field)
expect(created_field.type).to be_a(GraphQL::ScalarType)
expect(created_field.type.name).to eq('Int')
end
it "creates fields for String attributes" do
debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { title: :string }, created_types)
created_field = debate_type.fields['title']
expect(created_field).to be_a(GraphQL::Field)
expect(created_field.type).to be_a(GraphQL::ScalarType)
expect(created_field.type.name).to eq('String')
end
it "creates connections for :belongs_to associations" do
user_type = GraphQL::ApiTypesCreator.create_type(User, { id: :integer }, created_types)
debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { author: User }, created_types)
connection = debate_type.fields['author']
expect(connection).to be_a(GraphQL::Field)
expect(connection.type).to eq(user_type)
expect(connection.name).to eq('author')
end
it "creates connections for :has_one associations" do
user_type = GraphQL::ApiTypesCreator.create_type(User, { organization: Organization }, created_types)
organization_type = GraphQL::ApiTypesCreator.create_type(Organization, { id: :integer }, created_types)
connection = user_type.fields['organization']
expect(connection).to be_a(GraphQL::Field)
expect(connection.type).to eq(organization_type)
expect(connection.name).to eq('organization')
end
it "creates connections for :has_many associations" do
comment_type = GraphQL::ApiTypesCreator.create_type(Comment, { id: :integer }, created_types)
debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { comments: [Comment] }, created_types)
connection = debate_type.fields['comments']
expect(connection).to be_a(GraphQL::Field)
expect(connection.type).to eq(comment_type.connection_type)
expect(connection.name).to eq('comments')
end
end
end

View File

@@ -0,0 +1,35 @@
require 'rails_helper'
describe GraphQL::QueryTypeCreator do
let(:api_type_definitions) do
{
ProposalNotification => { fields: { title: :string } },
Proposal => { fields: { id: :integer, title: :string } }
}
end
let(:api_types) { GraphQL::ApiTypesCreator.create(api_type_definitions) }
describe "::create" do
let(:query_type) { GraphQL::QueryTypeCreator.create(api_types) }
it 'creates a QueryType with fields to retrieve single objects whose model fields included an ID' do
field = query_type.fields['proposal']
expect(field).to be_a(GraphQL::Field)
expect(field.type).to eq(api_types[Proposal])
expect(field.name).to eq('proposal')
end
it 'creates a QueryType without fields to retrieve single objects whose model fields did not include an ID' do
expect(query_type.fields['proposal_notification']).to be_nil
end
it "creates a QueryType with connections to retrieve collections of objects" do
connection = query_type.fields['proposals']
expect(connection).to be_a(GraphQL::Field)
expect(connection.type).to eq(api_types[Proposal].connection_type)
expect(connection.name).to eq('proposals')
end
end
end

674
spec/lib/graphql_spec.rb Normal file
View File

@@ -0,0 +1,674 @@
require 'rails_helper'
api_types = GraphQL::ApiTypesCreator.create(API_TYPE_DEFINITIONS)
query_type = GraphQL::QueryTypeCreator.create(api_types)
ConsulSchema = GraphQL::Schema.define do
query query_type
max_depth 12
end
def execute(query_string, context = {}, variables = {})
ConsulSchema.execute(query_string, context: context, variables: variables)
end
def dig(response, path)
response.dig(*path.split('.'))
end
def hidden_field?(response, field_name)
data_is_empty = response['data'].nil?
error_is_present = ((response['errors'].first['message'] =~ /Field '#{field_name}' doesn't exist on type '[[:alnum:]]*'/) == 0)
data_is_empty && error_is_present
end
def extract_fields(response, collection_name, field_chain)
fields = field_chain.split('.')
dig(response, "data.#{collection_name}.edges").collect do |node|
begin
if fields.size > 1
node['node'][fields.first][fields.second]
else
node['node'][fields.first]
end
rescue NoMethodError
end
end.compact
end
describe 'ConsulSchema' do
let(:user) { create(:user) }
let(:proposal) { create(:proposal, author: user) }
it 'returns fields of Int type' do
response = execute("{ proposal(id: #{proposal.id}) { id } }")
expect(dig(response, 'data.proposal.id')).to eq(proposal.id)
end
it 'returns fields of String type' do
response = execute("{ proposal(id: #{proposal.id}) { title } }")
expect(dig(response, 'data.proposal.title')).to eq(proposal.title)
end
xit 'returns has_one associations' do
organization = create(:organization)
response = execute("{ user(id: #{organization.user_id}) { organization { name } } }")
expect(dig(response, 'data.user.organization.name')).to eq(organization.name)
end
it 'returns belongs_to associations' do
response = execute("{ proposal(id: #{proposal.id}) { public_author { username } } }")
expect(dig(response, 'data.proposal.public_author.username')).to eq(proposal.public_author.username)
end
it 'returns has_many associations' do
comments_author = create(:user)
comment_1 = create(:comment, author: comments_author, commentable: proposal)
comment_2 = create(:comment, author: comments_author, commentable: proposal)
response = execute("{ proposal(id: #{proposal.id}) { comments { edges { node { body } } } } }")
comments = dig(response, 'data.proposal.comments.edges').collect { |edge| edge['node'] }
comment_bodies = comments.collect { |comment| comment['body'] }
expect(comment_bodies).to match_array([comment_1.body, comment_2.body])
end
xit 'executes deeply nested queries' do
org_user = create(:user)
organization = create(:organization, user: org_user)
org_proposal = create(:proposal, author: org_user)
response = execute("{ proposal(id: #{org_proposal.id}) { public_author { organization { name } } } }")
expect(dig(response, 'data.proposal.public_author.organization.name')).to eq(organization.name)
end
it 'hides confidential fields of Int type' do
response = execute("{ user(id: #{user.id}) { failed_census_calls_count } }")
expect(hidden_field?(response, 'failed_census_calls_count')).to be_truthy
end
it 'hides confidential fields of String type' do
response = execute("{ user(id: #{user.id}) { encrypted_password } }")
expect(hidden_field?(response, 'encrypted_password')).to be_truthy
end
xit 'hides confidential has_one associations' do
user.administrator = create(:administrator)
response = execute("{ user(id: #{user.id}) { administrator { id } } }")
expect(hidden_field?(response, 'administrator')).to be_truthy
end
it 'hides confidential belongs_to associations' do
create(:failed_census_call, user: user)
response = execute("{ user(id: #{user.id}) { failed_census_calls { id } } }")
expect(hidden_field?(response, 'failed_census_calls')).to be_truthy
end
it 'hides confidential has_many associations' do
create(:direct_message, sender: user)
response = execute("{ user(id: #{user.id}) { direct_messages_sent { id } } }")
expect(hidden_field?(response, 'direct_messages_sent')).to be_truthy
end
it 'hides confidential fields inside deeply nested queries' do
response = execute("{ proposals(first: 1) { edges { node { public_author { encrypted_password } } } } }")
expect(hidden_field?(response, 'encrypted_password')).to be_truthy
end
describe 'Users' do
let(:user) { create(:user, public_activity: false) }
it 'does not link debates if activity is not public' do
create(:debate, author: user)
response = execute("{ user(id: #{user.id}) { public_debates { edges { node { title } } } } }")
received_debates = dig(response, 'data.user.public_debates.edges')
expect(received_debates).to eq []
end
it 'does not link proposals if activity is not public' do
create(:proposal, author: user)
response = execute("{ user(id: #{user.id}) { public_proposals { edges { node { title } } } } }")
received_proposals = dig(response, 'data.user.public_proposals.edges')
expect(received_proposals).to eq []
end
it 'does not link comments if activity is not public' do
create(:comment, author: user)
response = execute("{ user(id: #{user.id}) { public_comments { edges { node { body } } } } }")
received_comments = dig(response, 'data.user.public_comments.edges')
expect(received_comments).to eq []
end
end
describe 'Proposals' do
it 'does not include hidden proposals' do
visible_proposal = create(:proposal)
hidden_proposal = create(:proposal, :hidden)
response = execute('{ proposals { edges { node { title } } } }')
received_titles = extract_fields(response, 'proposals', 'title')
expect(received_titles).to match_array [visible_proposal.title]
end
xit 'only returns proposals of the Human Rights proceeding' do
proposal = create(:proposal)
human_rights_proposal = create(:proposal, proceeding: 'Derechos Humanos', sub_proceeding: 'Right to have a job')
other_proceeding_proposal = create(:proposal)
other_proceeding_proposal.update_attribute(:proceeding, 'Another proceeding')
response = execute('{ proposals { edges { node { title } } } }')
received_titles = extract_fields(response, 'proposals', 'title')
expect(received_titles).to match_array [proposal.title, human_rights_proposal.title]
end
it 'includes proposals of authors even if public activity is set to false' do
visible_author = create(:user, public_activity: true)
hidden_author = create(:user, public_activity: false)
visible_proposal = create(:proposal, author: visible_author)
hidden_proposal = create(:proposal, author: hidden_author)
response = execute('{ proposals { edges { node { title } } } }')
received_titles = extract_fields(response, 'proposals', 'title')
expect(received_titles).to match_array [visible_proposal.title, hidden_proposal.title]
end
it 'does not link author if public activity is set to false' do
visible_author = create(:user, public_activity: true)
hidden_author = create(:user, public_activity: false)
visible_proposal = create(:proposal, author: visible_author)
hidden_proposal = create(:proposal, author: hidden_author)
response = execute('{ proposals { edges { node { public_author { username } } } } }')
received_authors = extract_fields(response, 'proposals', 'public_author.username')
expect(received_authors).to match_array [visible_author.username]
end
it 'only returns date and hour for created_at' do
created_at = Time.zone.parse("2017-12-31 9:30:15")
create(:proposal, created_at: created_at)
response = execute('{ proposals { edges { node { public_created_at } } } }')
received_timestamps = extract_fields(response, 'proposals', 'public_created_at')
expect(Time.zone.parse(received_timestamps.first)).to eq Time.zone.parse("2017-12-31 9:00:00")
end
it 'only retruns tags with kind nil or category' do
tag = create(:tag, name: 'Parks')
category_tag = create(:tag, name: 'Health', kind: 'category')
admin_tag = create(:tag, name: 'Admin tag', kind: 'admin')
proposal = create(:proposal, tag_list: 'Parks, Health, Admin tag')
response = execute("{ proposal(id: #{proposal.id}) { tags { edges { node { name } } } } }")
received_tags = dig(response, 'data.proposal.tags.edges').map { |node| node['node']['name'] }
expect(received_tags).to match_array ['Parks', 'Health']
end
end
describe 'Debates' do
it 'does not include hidden debates' do
visible_debate = create(:debate)
hidden_debate = create(:debate, :hidden)
response = execute('{ debates { edges { node { title } } } }')
received_titles = extract_fields(response, 'debates', 'title')
expect(received_titles).to match_array [visible_debate.title]
end
it 'includes debates of authors even if public activity is set to false' do
visible_author = create(:user, public_activity: true)
hidden_author = create(:user, public_activity: false)
visible_debate = create(:debate, author: visible_author)
hidden_debate = create(:debate, author: hidden_author)
response = execute('{ debates { edges { node { title } } } }')
received_titles = extract_fields(response, 'debates', 'title')
expect(received_titles).to match_array [visible_debate.title, hidden_debate.title]
end
it 'does not link author if public activity is set to false' do
visible_author = create(:user, public_activity: true)
hidden_author = create(:user, public_activity: false)
visible_debate = create(:debate, author: visible_author)
hidden_debate = create(:debate, author: hidden_author)
response = execute('{ debates { edges { node { public_author { username } } } } }')
received_authors = extract_fields(response, 'debates', 'public_author.username')
expect(received_authors).to match_array [visible_author.username]
end
it 'only returns date and hour for created_at' do
created_at = Time.zone.parse("2017-12-31 9:30:15")
create(:debate, created_at: created_at)
response = execute('{ debates { edges { node { public_created_at } } } }')
received_timestamps = extract_fields(response, 'debates', 'public_created_at')
expect(Time.zone.parse(received_timestamps.first)).to eq Time.zone.parse("2017-12-31 9:00:00")
end
it 'only retruns tags with kind nil or category' do
tag = create(:tag, name: 'Parks')
category_tag = create(:tag, name: 'Health', kind: 'category')
admin_tag = create(:tag, name: 'Admin tag', kind: 'admin')
debate = create(:debate, tag_list: 'Parks, Health, Admin tag')
response = execute("{ debate(id: #{debate.id}) { tags { edges { node { name } } } } }")
received_tags = dig(response, 'data.debate.tags.edges').map { |node| node['node']['name'] }
expect(received_tags).to match_array ['Parks', 'Health']
end
end
describe 'Comments' do
it 'only returns comments from proposals and debates' do
proposal_comment = create(:comment, commentable: create(:proposal))
debate_comment = create(:comment, commentable: create(:debate))
spending_proposal_comment = build(:comment, commentable: create(:spending_proposal)).save(skip_validation: true)
response = execute('{ comments { edges { node { commentable_type } } } }')
received_commentables = extract_fields(response, 'comments', 'commentable_type')
expect(received_commentables).to match_array ['Proposal', 'Debate']
end
it 'displays comments of authors even if public activity is set to false' do
visible_author = create(:user, public_activity: true)
hidden_author = create(:user, public_activity: false)
visible_comment = create(:comment, user: visible_author)
hidden_comment = create(:comment, user: hidden_author)
response = execute('{ comments { edges { node { body } } } }')
received_comments = extract_fields(response, 'comments', 'body')
expect(received_comments).to match_array [visible_comment.body, hidden_comment.body]
end
it 'does not link author if public activity is set to false' do
visible_author = create(:user, public_activity: true)
hidden_author = create(:user, public_activity: false)
visible_comment = create(:comment, author: visible_author)
hidden_comment = create(:comment, author: hidden_author)
response = execute('{ comments { edges { node { public_author { username } } } } }')
received_authors = extract_fields(response, 'comments', 'public_author.username')
expect(received_authors).to match_array [visible_author.username]
end
it 'does not include hidden comments' do
visible_comment = create(:comment)
hidden_comment = create(:comment, hidden_at: Time.now)
response = execute('{ comments { edges { node { body } } } }')
received_comments = extract_fields(response, 'comments', 'body')
expect(received_comments).to match_array [visible_comment.body]
end
it 'does not include comments from hidden proposals' do
visible_proposal = create(:proposal)
hidden_proposal = create(:proposal, hidden_at: Time.now)
visible_proposal_comment = create(:comment, commentable: visible_proposal)
hidden_proposal_comment = create(:comment, commentable: hidden_proposal)
response = execute('{ comments { edges { node { body } } } }')
received_comments = extract_fields(response, 'comments', 'body')
expect(received_comments).to match_array [visible_proposal_comment.body]
end
it 'does not include comments from hidden debates' do
visible_debate = create(:debate)
hidden_debate = create(:debate, hidden_at: Time.now)
visible_debate_comment = create(:comment, commentable: visible_debate)
hidden_debate_comment = create(:comment, commentable: hidden_debate)
response = execute('{ comments { edges { node { body } } } }')
received_comments = extract_fields(response, 'comments', 'body')
expect(received_comments).to match_array [visible_debate_comment.body]
end
it 'does not include comments of debates that are not public' do
not_public_debate = create(:debate, :hidden)
not_public_debate_comment = create(:comment, commentable: not_public_debate)
allow(Comment).to receive(:public_for_api).and_return([])
response = execute('{ comments { edges { node { body } } } }')
received_comments = extract_fields(response, 'comments', 'body')
expect(received_comments).to_not include(not_public_debate_comment.body)
end
it 'does not include comments of proposals that are not public' do
not_public_proposal = create(:proposal)
not_public_proposal_comment = create(:comment, commentable: not_public_proposal)
allow(Comment).to receive(:public_for_api).and_return([])
response = execute('{ comments { edges { node { body } } } }')
received_comments = extract_fields(response, 'comments', 'body')
expect(received_comments).to_not include(not_public_proposal_comment.body)
end
it 'only returns date and hour for created_at' do
created_at = Time.zone.parse("2017-12-31 9:30:15")
create(:comment, created_at: created_at)
response = execute('{ comments { edges { node { public_created_at } } } }')
received_timestamps = extract_fields(response, 'comments', 'public_created_at')
expect(Time.zone.parse(received_timestamps.first)).to eq Time.zone.parse("2017-12-31 9:00:00")
end
end
describe 'Geozones' do
it 'returns geozones' do
geozone_names = [ create(:geozone), create(:geozone) ].map { |geozone| geozone.name }
response = execute('{ geozones { edges { node { name } } } }')
received_names = extract_fields(response, 'geozones', 'name')
expect(received_names).to match_array geozone_names
end
end
describe 'Proposal notifications' do
it 'does not include proposal notifications for hidden proposals' do
visible_proposal = create(:proposal)
hidden_proposal = create(:proposal, :hidden)
visible_proposal_notification = create(:proposal_notification, proposal: visible_proposal)
hidden_proposal_notification = create(:proposal_notification, proposal: hidden_proposal)
response = execute('{ proposal_notifications { edges { node { title } } } }')
received_notifications = extract_fields(response, 'proposal_notifications', 'title')
expect(received_notifications).to match_array [visible_proposal_notification.title]
end
it 'does not include proposal notifications for proposals that are not public' do
not_public_proposal = create(:proposal)
not_public_proposal_notification = create(:proposal_notification, proposal: not_public_proposal)
allow(ProposalNotification).to receive(:public_for_api).and_return([])
response = execute('{ proposal_notifications { edges { node { title } } } }')
received_notifications = extract_fields(response, 'proposal_notifications', 'title')
expect(received_notifications).to_not include(not_public_proposal_notification.title)
end
it 'only returns date and hour for created_at' do
created_at = Time.zone.parse("2017-12-31 9:30:15")
create(:proposal_notification, created_at: created_at)
response = execute('{ proposal_notifications { edges { node { public_created_at } } } }')
received_timestamps = extract_fields(response, 'proposal_notifications', 'public_created_at')
expect(Time.zone.parse(received_timestamps.first)).to eq Time.zone.parse("2017-12-31 9:00:00")
end
it 'only links proposal if public' do
visible_proposal = create(:proposal)
hidden_proposal = create(:proposal, :hidden)
visible_proposal_notification = create(:proposal_notification, proposal: visible_proposal)
hidden_proposal_notification = create(:proposal_notification, proposal: hidden_proposal)
response = execute('{ proposal_notifications { edges { node { proposal { title } } } } }')
received_proposals = extract_fields(response, 'proposal_notifications', 'proposal.title')
expect(received_proposals).to match_array [visible_proposal.title]
end
end
describe 'Tags' do
it 'only display tags with kind nil or category' do
tag = create(:tag, name: 'Parks')
category_tag = create(:tag, name: 'Health', kind: 'category')
admin_tag = create(:tag, name: 'Admin tag', kind: 'admin')
proposal = create(:proposal, tag_list: 'Parks')
proposal = create(:proposal, tag_list: 'Health')
proposal = create(:proposal, tag_list: 'Admin tag')
response = execute('{ tags { edges { node { name } } } }')
received_tags = extract_fields(response, 'tags', 'name')
expect(received_tags).to match_array ['Parks', 'Health']
end
xit 'uppercase and lowercase tags work ok together for proposals' do
create(:tag, name: 'Health')
create(:tag, name: 'health')
create(:proposal, tag_list: 'health')
create(:proposal, tag_list: 'Health')
response = execute('{ tags { edges { node { name } } } }')
received_tags = extract_fields(response, 'tags', 'name')
expect(received_tags).to match_array ['Health', 'health']
end
xit 'uppercase and lowercase tags work ok together for debates' do
create(:tag, name: 'Health')
create(:tag, name: 'health')
create(:debate, tag_list: 'Health')
create(:debate, tag_list: 'health')
response = execute('{ tags { edges { node { name } } } }')
received_tags = extract_fields(response, 'tags', 'name')
expect(received_tags).to match_array ['Health', 'health']
end
it 'does not display tags for hidden proposals' do
proposal = create(:proposal, tag_list: 'Health')
hidden_proposal = create(:proposal, :hidden, tag_list: 'SPAM')
response = execute('{ tags { edges { node { name } } } }')
received_tags = extract_fields(response, 'tags', 'name')
expect(received_tags).to match_array ['Health']
end
it 'does not display tags for hidden debates' do
debate = create(:debate, tag_list: 'Health, Transportation')
hidden_debate = create(:debate, :hidden, tag_list: 'SPAM')
response = execute('{ tags { edges { node { name } } } }')
received_tags = extract_fields(response, 'tags', 'name')
expect(received_tags).to match_array ['Health', 'Transportation']
end
xit "does not display tags for proceeding's proposals" do
valid_proceeding_proposal = create(:proposal, proceeding: 'Derechos Humanos', sub_proceeding: 'Right to a Home', tag_list: 'Health')
invalid_proceeding_proposal = create(:proposal, tag_list: 'Animals')
invalid_proceeding_proposal.update_attribute('proceeding', 'Random')
response = execute('{ tags { edges { node { name } } } }')
received_tags = extract_fields(response, 'tags', 'name')
expect(received_tags).to match_array ['Health']
end
it 'does not display tags for taggings that are not public' do
proposal = create(:proposal, tag_list: 'Health')
allow(ActsAsTaggableOn::Tag).to receive(:public_for_api).and_return([])
response = execute('{ tags { edges { node { name } } } }')
received_tags = extract_fields(response, 'tags', 'name')
expect(received_tags).to_not include('Health')
end
end
describe 'Votes' do
it 'only returns votes from proposals, debates and comments' do
proposal = create(:proposal)
debate = create(:debate)
comment = create(:comment)
spending_proposal = create(:spending_proposal)
proposal_vote = create(:vote, votable: proposal)
debate_vote = create(:vote, votable: debate)
comment_vote = create(:vote, votable: comment)
spending_proposal_vote = create(:vote, votable: spending_proposal)
response = execute('{ votes { edges { node { votable_type } } } }')
received_votables = extract_fields(response, 'votes', 'votable_type')
expect(received_votables).to match_array ['Proposal', 'Debate', 'Comment']
end
it 'does not include votes from hidden debates' do
visible_debate = create(:debate)
hidden_debate = create(:debate, :hidden)
visible_debate_vote = create(:vote, votable: visible_debate)
hidden_debate_vote = create(:vote, votable: hidden_debate)
response = execute('{ votes { edges { node { votable_id } } } }')
received_debates = extract_fields(response, 'votes', 'votable_id')
expect(received_debates).to match_array [visible_debate.id]
end
it 'does not include votes of hidden proposals' do
visible_proposal = create(:proposal)
hidden_proposal = create(:proposal, hidden_at: Time.now)
visible_proposal_vote = create(:vote, votable: visible_proposal)
hidden_proposal_vote = create(:vote, votable: hidden_proposal)
response = execute('{ votes { edges { node { votable_id } } } }')
received_proposals = extract_fields(response, 'votes', 'votable_id')
expect(received_proposals).to match_array [visible_proposal.id]
end
it 'does not include votes of hidden comments' do
visible_comment = create(:comment)
hidden_comment = create(:comment, hidden_at: Time.now)
visible_comment_vote = create(:vote, votable: visible_comment)
hidden_comment_vote = create(:vote, votable: hidden_comment)
response = execute('{ votes { edges { node { votable_id } } } }')
received_comments = extract_fields(response, 'votes', 'votable_id')
expect(received_comments).to match_array [visible_comment.id]
end
it 'does not include votes of comments from a hidden proposal' do
visible_proposal = create(:proposal)
hidden_proposal = create(:proposal, :hidden)
visible_proposal_comment = create(:comment, commentable: visible_proposal)
hidden_proposal_comment = create(:comment, commentable: hidden_proposal)
visible_proposal_comment_vote = create(:vote, votable: visible_proposal_comment)
hidden_proposal_comment_vote = create(:vote, votable: hidden_proposal_comment)
response = execute('{ votes { edges { node { votable_id } } } }')
received_votables = extract_fields(response, 'votes', 'votable_id')
expect(received_votables).to match_array [visible_proposal_comment.id]
end
it 'does not include votes of comments from a hidden debate' do
visible_debate = create(:debate)
hidden_debate = create(:debate, :hidden)
visible_debate_comment = create(:comment, commentable: visible_debate)
hidden_debate_comment = create(:comment, commentable: hidden_debate)
visible_debate_comment_vote = create(:vote, votable: visible_debate_comment)
hidden_debate_comment_vote = create(:vote, votable: hidden_debate_comment)
response = execute('{ votes { edges { node { votable_id } } } }')
received_votables = extract_fields(response, 'votes', 'votable_id')
expect(received_votables).to match_array [visible_debate_comment.id]
end
it 'does not include votes of debates that are not public' do
not_public_debate = create(:debate)
allow(Vote).to receive(:public_for_api).and_return([])
not_public_debate_vote = create(:vote, votable: not_public_debate)
response = execute('{ votes { edges { node { votable_id } } } }')
received_votables = extract_fields(response, 'votes', 'votable_id')
expect(received_votables).to_not include(not_public_debate.id)
end
it 'does not include votes of a hidden proposals' do
not_public_proposal = create(:proposal)
allow(Vote).to receive(:public_for_api).and_return([])
not_public_proposal_vote = create(:vote, votable: not_public_proposal)
response = execute('{ votes { edges { node { votable_id } } } }')
received_votables = extract_fields(response, 'votes', 'votable_id')
expect(received_votables).to_not include(not_public_proposal.id)
end
it 'does not include votes of a hidden comments' do
not_public_comment = create(:comment)
allow(Vote).to receive(:public_for_api).and_return([])
not_public_comment_vote = create(:vote, votable: not_public_comment)
response = execute('{ votes { edges { node { votable_id } } } }')
received_votables = extract_fields(response, 'votes', 'votable_id')
expect(received_votables).to_not include(not_public_comment.id)
end
it 'only returns date and hour for created_at' do
created_at = Time.zone.parse("2017-12-31 9:30:15")
create(:vote, created_at: created_at)
response = execute('{ votes { edges { node { public_created_at } } } }')
received_timestamps = extract_fields(response, 'votes', 'public_created_at')
expect(Time.zone.parse(received_timestamps.first)).to eq Time.zone.parse("2017-12-31 9:00:00")
end
end
end

View File

@@ -4,6 +4,8 @@ describe Comment do
let(:comment) { build(:comment) }
it_behaves_like "has_public_author"
it "is valid" do
expect(comment).to be_valid
end
@@ -132,4 +134,58 @@ describe Comment do
end
end
describe "public_for_api scope" do
it "returns comments" do
comment = create(:comment)
expect(Comment.public_for_api).to include(comment)
end
it "does not return hidden comments" do
hidden_comment = create(:comment, :hidden)
expect(Comment.public_for_api).not_to include(hidden_comment)
end
it "returns comments on debates" do
debate = create(:debate)
comment = create(:comment, commentable: debate)
expect(Comment.public_for_api).to include(comment)
end
it "does not return comments on hidden debates" do
hidden_debate = create(:debate, :hidden)
comment = create(:comment, commentable: hidden_debate)
expect(Comment.public_for_api).not_to include(comment)
end
it "returns comments on proposals" do
proposal = create(:proposal)
comment = create(:comment, commentable: proposal)
expect(Comment.public_for_api).to include(comment)
end
it "does not return comments on hidden proposals" do
hidden_proposal = create(:proposal, :hidden)
comment = create(:comment, commentable: hidden_proposal)
expect(Comment.public_for_api).not_to include(comment)
end
it 'does not return comments on elements which are not debates or proposals' do
budget_investment = create(:budget_investment)
comment = create(:comment, commentable: budget_investment)
expect(Comment.public_for_api).not_to include(comment)
end
it 'does not return comments with no commentable' do
comment = build(:comment, commentable: nil).save!(validate: false)
expect(Comment.public_for_api).to_not include(comment)
end
end
end

View File

@@ -0,0 +1,21 @@
require 'spec_helper'
shared_examples_for 'has_public_author' do
let(:model) { described_class }
describe 'public_author' do
it "returns author if author's activity is public" do
author = create(:user, public_activity: true)
authored_element = create(model.to_s.underscore.to_sym, author: author)
expect(authored_element.public_author).to eq(author)
end
it "returns nil if author's activity is private" do
author = create(:user, public_activity: false)
authored_element = create(model.to_s.underscore.to_sym, author: author)
expect(authored_element.public_author).to be_nil
end
end
end

View File

@@ -4,6 +4,8 @@ require 'rails_helper'
describe Debate do
let(:debate) { build(:debate) }
it_behaves_like "has_public_author"
it "should be valid" do
expect(debate).to be_valid
end
@@ -700,4 +702,16 @@ describe Debate do
end
end
describe 'public_for_api scope' do
it 'returns debates' do
debate = create(:debate)
expect(Debate.public_for_api).to include(debate)
end
it 'does not return hidden debates' do
debate = create(:debate, :hidden)
expect(Debate.public_for_api).to_not include(debate)
end
end
end

View File

@@ -22,6 +22,28 @@ describe ProposalNotification do
expect(notification).to_not be_valid
end
describe "public_for_api scope" do
it "returns proposal notifications" do
proposal = create(:proposal)
notification = create(:proposal_notification, proposal: proposal)
expect(ProposalNotification.public_for_api).to include(notification)
end
it "blocks proposal notifications whose proposal is hidden" do
proposal = create(:proposal, :hidden)
notification = create(:proposal_notification, proposal: proposal)
expect(ProposalNotification.public_for_api).not_to include(notification)
end
it "blocks proposal notifications without proposal" do
proposal = build(:proposal_notification, proposal: nil).save!(validate: false)
expect(ProposalNotification.public_for_api).not_to include(notification)
end
end
describe "minimum interval between notifications" do
before(:each) do

View File

@@ -4,6 +4,8 @@ require 'rails_helper'
describe Proposal do
let(:proposal) { build(:proposal) }
it_behaves_like "has_public_author"
it "should be valid" do
expect(proposal).to be_valid
end
@@ -843,4 +845,16 @@ describe Proposal do
end
end
describe 'public_for_api scope' do
it 'returns proposals' do
proposal = create(:proposal)
expect(Proposal.public_for_api).to include(proposal)
end
it 'does not return hidden proposals' do
proposal = create(:proposal, :hidden)
expect(Proposal.public_for_api).to_not include(proposal)
end
end
end

View File

@@ -40,4 +40,77 @@ describe 'Vote' do
expect(vote.value).to eq(false)
end
end
describe 'public_for_api scope' do
it 'returns votes on debates' do
debate = create(:debate)
vote = create(:vote, votable: debate)
expect(Vote.public_for_api).to include(vote)
end
it 'blocks votes on hidden debates' do
debate = create(:debate, :hidden)
vote = create(:vote, votable: debate)
expect(Vote.public_for_api).not_to include(vote)
end
it 'returns votes on proposals' do
proposal = create(:proposal)
vote = create(:vote, votable: proposal)
expect(Vote.public_for_api).to include(vote)
end
it 'blocks votes on hidden proposals' do
proposal = create(:proposal, :hidden)
vote = create(:vote, votable: proposal)
expect(Vote.public_for_api).not_to include(vote)
end
it 'returns votes on comments' do
comment = create(:comment)
vote = create(:vote, votable: comment)
expect(Vote.public_for_api).to include(vote)
end
it 'blocks votes on hidden comments' do
comment = create(:comment, :hidden)
vote = create(:vote, votable: comment)
expect(Vote.public_for_api).not_to include(vote)
end
it 'blocks votes on comments on hidden proposals' do
hidden_proposal = create(:proposal, :hidden)
comment_on_hidden_proposal = create(:comment, commentable: hidden_proposal)
vote = create(:vote, votable: comment_on_hidden_proposal)
expect(Vote.public_for_api).to_not include(vote)
end
it 'blocks votes on comments on hidden debates' do
hidden_debate = create(:debate, :hidden)
comment_on_hidden_debate = create(:comment, commentable: hidden_debate)
vote = create(:vote, votable: comment_on_hidden_debate)
expect(Vote.public_for_api).to_not include(vote)
end
it 'blocks any other kind of votes' do
spending_proposal = create(:spending_proposal)
vote = create(:vote, votable: spending_proposal)
expect(Vote.public_for_api).not_to include(vote)
end
it 'blocks votes without votable' do
vote = build(:vote, votable: nil).save!(validate: false)
expect(Vote.public_for_api).not_to include(vote)
end
end
end

View File

@@ -3,6 +3,7 @@ require 'database_cleaner'
require 'email_spec'
require 'devise'
require 'knapsack'
Dir["./spec/models/concerns/*.rb"].each { |f| require f }
Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
RSpec.configure do |config|