From b6062eb59f28db9c6c28234cfd71199863479091 Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Tue, 31 Oct 2017 10:26:06 +0100 Subject: [PATCH 01/43] Make accordion truely resuable by adding dynamic open param values --- .../accordion/accordion-component.jsx | 20 +++------ .../app/components/accordion/accordion.js | 44 ++++++++++--------- .../ndcs-country-accordion-component.jsx | 1 + 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/app/javascript/app/components/accordion/accordion-component.jsx b/app/javascript/app/components/accordion/accordion-component.jsx index fa8db02904..3984e83fb5 100644 --- a/app/javascript/app/components/accordion/accordion-component.jsx +++ b/app/javascript/app/components/accordion/accordion-component.jsx @@ -10,24 +10,16 @@ import styles from './accordion-styles.scss'; class Accordion extends PureComponent { render() { - const { - className, - data, - handleOnClick, - activeSection, - compare - } = this.props; + const { className, data, handleOnClick, openSlug, compare } = this.props; return (
{data.map((section, index) => { let isOpen = index === 0; - if (activeSection) { - if (activeSection !== 'none') { - const isActiveInResults = data.some( - d => d.slug === activeSection - ); + if (openSlug) { + if (openSlug !== 'none') { + const isActiveInResults = data.some(d => d.slug === openSlug); isOpen = - activeSection === section.slug || + openSlug === section.slug || (index === 0 && !isActiveInResults); } else { isOpen = false; @@ -93,7 +85,7 @@ class Accordion extends PureComponent { Accordion.propTypes = { className: PropTypes.string, - activeSection: PropTypes.string, + openSlug: PropTypes.string, handleOnClick: PropTypes.func, data: PropTypes.arrayOf( PropTypes.shape({ diff --git a/app/javascript/app/components/accordion/accordion.js b/app/javascript/app/components/accordion/accordion.js index f1f81c0b19..0f01e4ca56 100644 --- a/app/javascript/app/components/accordion/accordion.js +++ b/app/javascript/app/components/accordion/accordion.js @@ -1,41 +1,45 @@ -import { createElement } from 'react'; +import { createElement, PureComponent } from 'react'; import { withRouter } from 'react-router'; import Proptypes from 'prop-types'; import { connect } from 'react-redux'; import qs from 'query-string'; +import { getLocationParamUpdated } from 'utils/navigation'; + import AccordionComponent from './accordion-component'; -const mapStateToProps = (state, { location }) => { +const mapStateToProps = (state, { location, param }) => { const search = qs.parse(location.search); - const activeSection = search.activeSection ? search.activeSection : null; + const openSlug = search[param] || null; return { - activeSection + openSlug }; }; -const AccordionContainer = props => { - const handleOnClick = slug => { - const { location, history } = props; +class AccordionContainer extends PureComponent { + handleOnClick = slug => { + const { param, location } = this.props; const search = qs.parse(location.search); - const newSlug = - search.activeSection === slug || !search.activeSection ? 'none' : slug; - const newSearch = { ...search, activeSection: newSlug }; + const newSlug = search[param] === slug || !search[param] ? 'none' : slug; + this.updateUrlParam({ name: param, value: newSlug }); + }; - history.replace({ - pathname: location.pathname, - search: qs.stringify(newSearch) - }); + updateUrlParam = (params, clear) => { + const { history, location } = this.props; + history.replace(getLocationParamUpdated(location, params, clear)); }; - return createElement(AccordionComponent, { - ...props, - handleOnClick - }); -}; + render() { + return createElement(AccordionComponent, { + handleOnClick: this.handleOnClick, + ...this.props + }); + } +} AccordionContainer.propTypes = { location: Proptypes.object, - history: Proptypes.object + history: Proptypes.object, + param: Proptypes.string }; export default withRouter(connect(mapStateToProps, null)(AccordionContainer)); diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx index d12e1eb761..412e1a1089 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx @@ -23,6 +23,7 @@ class NdcsCountryAccordion extends PureComponent { {ndcsData && ndcsData.length > 0 && ( Date: Tue, 31 Oct 2017 10:38:34 +0100 Subject: [PATCH 02/43] Reusable children inside accordion --- .../accordion.1/accordion-component.jsx | 104 ++++++++++++++++++ .../accordion.1/accordion-styles.scss | 92 ++++++++++++++++ .../app/components/accordion.1/accordion.js | 45 ++++++++ .../accordion/accordion-component.jsx | 42 ++----- .../ndcs-country-accordion-component.jsx | 9 +- 5 files changed, 257 insertions(+), 35 deletions(-) create mode 100644 app/javascript/app/components/accordion.1/accordion-component.jsx create mode 100644 app/javascript/app/components/accordion.1/accordion-styles.scss create mode 100644 app/javascript/app/components/accordion.1/accordion.js diff --git a/app/javascript/app/components/accordion.1/accordion-component.jsx b/app/javascript/app/components/accordion.1/accordion-component.jsx new file mode 100644 index 0000000000..3984e83fb5 --- /dev/null +++ b/app/javascript/app/components/accordion.1/accordion-component.jsx @@ -0,0 +1,104 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { Collapse } from 'react-collapse'; +import Icon from 'components/icon'; +import cx from 'classnames'; + +import dropdownArrow from 'assets/icons/dropdown-arrow.svg'; +import layout from 'styles/layout.scss'; +import styles from './accordion-styles.scss'; + +class Accordion extends PureComponent { + render() { + const { className, data, handleOnClick, openSlug, compare } = this.props; + return ( +
+ {data.map((section, index) => { + let isOpen = index === 0; + if (openSlug) { + if (openSlug !== 'none') { + const isActiveInResults = data.some(d => d.slug === openSlug); + isOpen = + openSlug === section.slug || + (index === 0 && !isActiveInResults); + } else { + isOpen = false; + } + } + return section.definitions.length ? ( +
+ + +
+
+
+ {section.definitions.map(def => ( +
+
+ {def.title} +
+ {def.descriptions.map(desc => ( +
+
+
+ ))} +
+ ))} +
+
+
+
+
+ ) : null; + })} +
+ ); + } +} + +Accordion.propTypes = { + className: PropTypes.string, + openSlug: PropTypes.string, + handleOnClick: PropTypes.func, + data: PropTypes.arrayOf( + PropTypes.shape({ + title: PropTypes.string, + slug: PropTypes.string, + definitions: PropTypes.array.isRequired + }) + ), + compare: PropTypes.bool +}; + +Accordion.defaultProps = { + data: [] +}; + +export default Accordion; diff --git a/app/javascript/app/components/accordion.1/accordion-styles.scss b/app/javascript/app/components/accordion.1/accordion-styles.scss new file mode 100644 index 0000000000..c8e8c461fa --- /dev/null +++ b/app/javascript/app/components/accordion.1/accordion-styles.scss @@ -0,0 +1,92 @@ +@import '~styles/settings'; +@import '~styles/layout.scss'; + +.accordion { + position: relative; +} + +.header { + width: 100%; + padding: 20px 0; + background-color: $white; + color: $theme-color; + font-size: $font-size-large; + font-family: $font-family-1; + border-bottom: solid 1px rgba($theme-color, 0.3); + cursor: pointer; + + &:focus { + outline: none; + } + + .title { + text-align: left; + position: relative; + padding-right: 30px; + } +} + +.iconArrow { + transition: transform 0.25s ease-in-out; + position: absolute; + right: 0; + top: calc(50% - 5px); + + &.isOpen { + transform: rotate(180deg); + } +} + +.accordionContent { + background-color: $light-gray; + border-bottom: solid 1px rgba($theme-color, 0.3); +} + +.definitionCompare { + @extend %grid; + + @include msGridColumns(1fr, 1fr, 1fr, 1fr); + + grid-template-columns: repeat(4, 1fr); + padding: 30px 0; + border-bottom: solid 1px rgba(#9090b2, 0.15); + + &:last-child { + border-bottom: 0; + } +} + +.definition { + @extend %grid; + + @include msGridColumns(1fr, 3fr); + + grid-template-columns: 1fr 3fr; + padding: 20px 0; + border-bottom: solid 1px rgba(#9090b2, 0.15); + + &:last-child { + border-bottom: 0; + } +} + +.definitionTitle { + margin-right: 40px; + color: $theme-color; + font-weight: $font-weight-bold; + font-family: $font-family-1; +} + +.definitionDesc { + color: $theme-color; + font-family: $font-family-1; + line-height: 1.4em; +} + +.loader { + min-height: 400px; + position: absolute; + top: 0; + left: 0; + right: 0; +} diff --git a/app/javascript/app/components/accordion.1/accordion.js b/app/javascript/app/components/accordion.1/accordion.js new file mode 100644 index 0000000000..0f01e4ca56 --- /dev/null +++ b/app/javascript/app/components/accordion.1/accordion.js @@ -0,0 +1,45 @@ +import { createElement, PureComponent } from 'react'; +import { withRouter } from 'react-router'; +import Proptypes from 'prop-types'; +import { connect } from 'react-redux'; +import qs from 'query-string'; +import { getLocationParamUpdated } from 'utils/navigation'; + +import AccordionComponent from './accordion-component'; + +const mapStateToProps = (state, { location, param }) => { + const search = qs.parse(location.search); + const openSlug = search[param] || null; + return { + openSlug + }; +}; + +class AccordionContainer extends PureComponent { + handleOnClick = slug => { + const { param, location } = this.props; + const search = qs.parse(location.search); + const newSlug = search[param] === slug || !search[param] ? 'none' : slug; + this.updateUrlParam({ name: param, value: newSlug }); + }; + + updateUrlParam = (params, clear) => { + const { history, location } = this.props; + history.replace(getLocationParamUpdated(location, params, clear)); + }; + + render() { + return createElement(AccordionComponent, { + handleOnClick: this.handleOnClick, + ...this.props + }); + } +} + +AccordionContainer.propTypes = { + location: Proptypes.object, + history: Proptypes.object, + param: Proptypes.string +}; + +export default withRouter(connect(mapStateToProps, null)(AccordionContainer)); diff --git a/app/javascript/app/components/accordion/accordion-component.jsx b/app/javascript/app/components/accordion/accordion-component.jsx index 3984e83fb5..9fb187caf1 100644 --- a/app/javascript/app/components/accordion/accordion-component.jsx +++ b/app/javascript/app/components/accordion/accordion-component.jsx @@ -10,7 +10,7 @@ import styles from './accordion-styles.scss'; class Accordion extends PureComponent { render() { - const { className, data, handleOnClick, openSlug, compare } = this.props; + const { className, data, handleOnClick, openSlug, children } = this.props; return (
{data.map((section, index) => { @@ -25,7 +25,7 @@ class Accordion extends PureComponent { isOpen = false; } } - return section.definitions.length ? ( + return (
-
-
-
- {section.definitions.map(def => ( -
-
- {def.title} -
- {def.descriptions.map(desc => ( -
-
-
- ))} -
- ))} -
-
-
+ {React.Children.map(children, (child, i) => { + if (i === index) return child; + return null; + })}
- ) : null; + ); })}
); @@ -94,7 +68,7 @@ Accordion.propTypes = { definitions: PropTypes.array.isRequired }) ), - compare: PropTypes.bool + children: PropTypes.node }; Accordion.defaultProps = { diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx index 412e1a1089..b1af2df143 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx @@ -27,7 +27,14 @@ class NdcsCountryAccordion extends PureComponent { data={ndcsData} loading={loading} compare={compare} - /> + > +
+ test1 +
+
+ test2 +
+
)}
); From 0fc5d212cc14997f004a8c4e95da7fbd10457bb0 Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Tue, 31 Oct 2017 12:19:50 +0100 Subject: [PATCH 03/43] Styles for nested accordions --- .../accordion.1/accordion-component.jsx | 104 ------------------ .../accordion.1/accordion-styles.scss | 92 ---------------- .../app/components/accordion.1/accordion.js | 45 -------- .../accordion/accordion-styles.scss | 53 --------- .../definition-list-component.jsx | 44 ++++++++ .../definition-list-styles.scss | 42 +++++++ .../definition-list/definition-list.js | 3 + .../ndcs-country-accordion-component.jsx | 23 ++-- .../ndcs-country-accordion-styles.scss | 5 + 9 files changed, 110 insertions(+), 301 deletions(-) delete mode 100644 app/javascript/app/components/accordion.1/accordion-component.jsx delete mode 100644 app/javascript/app/components/accordion.1/accordion-styles.scss delete mode 100644 app/javascript/app/components/accordion.1/accordion.js create mode 100644 app/javascript/app/components/definition-list/definition-list-component.jsx create mode 100644 app/javascript/app/components/definition-list/definition-list-styles.scss create mode 100644 app/javascript/app/components/definition-list/definition-list.js diff --git a/app/javascript/app/components/accordion.1/accordion-component.jsx b/app/javascript/app/components/accordion.1/accordion-component.jsx deleted file mode 100644 index 3984e83fb5..0000000000 --- a/app/javascript/app/components/accordion.1/accordion-component.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { Collapse } from 'react-collapse'; -import Icon from 'components/icon'; -import cx from 'classnames'; - -import dropdownArrow from 'assets/icons/dropdown-arrow.svg'; -import layout from 'styles/layout.scss'; -import styles from './accordion-styles.scss'; - -class Accordion extends PureComponent { - render() { - const { className, data, handleOnClick, openSlug, compare } = this.props; - return ( -
- {data.map((section, index) => { - let isOpen = index === 0; - if (openSlug) { - if (openSlug !== 'none') { - const isActiveInResults = data.some(d => d.slug === openSlug); - isOpen = - openSlug === section.slug || - (index === 0 && !isActiveInResults); - } else { - isOpen = false; - } - } - return section.definitions.length ? ( -
- - -
-
-
- {section.definitions.map(def => ( -
-
- {def.title} -
- {def.descriptions.map(desc => ( -
-
-
- ))} -
- ))} -
-
-
-
-
- ) : null; - })} -
- ); - } -} - -Accordion.propTypes = { - className: PropTypes.string, - openSlug: PropTypes.string, - handleOnClick: PropTypes.func, - data: PropTypes.arrayOf( - PropTypes.shape({ - title: PropTypes.string, - slug: PropTypes.string, - definitions: PropTypes.array.isRequired - }) - ), - compare: PropTypes.bool -}; - -Accordion.defaultProps = { - data: [] -}; - -export default Accordion; diff --git a/app/javascript/app/components/accordion.1/accordion-styles.scss b/app/javascript/app/components/accordion.1/accordion-styles.scss deleted file mode 100644 index c8e8c461fa..0000000000 --- a/app/javascript/app/components/accordion.1/accordion-styles.scss +++ /dev/null @@ -1,92 +0,0 @@ -@import '~styles/settings'; -@import '~styles/layout.scss'; - -.accordion { - position: relative; -} - -.header { - width: 100%; - padding: 20px 0; - background-color: $white; - color: $theme-color; - font-size: $font-size-large; - font-family: $font-family-1; - border-bottom: solid 1px rgba($theme-color, 0.3); - cursor: pointer; - - &:focus { - outline: none; - } - - .title { - text-align: left; - position: relative; - padding-right: 30px; - } -} - -.iconArrow { - transition: transform 0.25s ease-in-out; - position: absolute; - right: 0; - top: calc(50% - 5px); - - &.isOpen { - transform: rotate(180deg); - } -} - -.accordionContent { - background-color: $light-gray; - border-bottom: solid 1px rgba($theme-color, 0.3); -} - -.definitionCompare { - @extend %grid; - - @include msGridColumns(1fr, 1fr, 1fr, 1fr); - - grid-template-columns: repeat(4, 1fr); - padding: 30px 0; - border-bottom: solid 1px rgba(#9090b2, 0.15); - - &:last-child { - border-bottom: 0; - } -} - -.definition { - @extend %grid; - - @include msGridColumns(1fr, 3fr); - - grid-template-columns: 1fr 3fr; - padding: 20px 0; - border-bottom: solid 1px rgba(#9090b2, 0.15); - - &:last-child { - border-bottom: 0; - } -} - -.definitionTitle { - margin-right: 40px; - color: $theme-color; - font-weight: $font-weight-bold; - font-family: $font-family-1; -} - -.definitionDesc { - color: $theme-color; - font-family: $font-family-1; - line-height: 1.4em; -} - -.loader { - min-height: 400px; - position: absolute; - top: 0; - left: 0; - right: 0; -} diff --git a/app/javascript/app/components/accordion.1/accordion.js b/app/javascript/app/components/accordion.1/accordion.js deleted file mode 100644 index 0f01e4ca56..0000000000 --- a/app/javascript/app/components/accordion.1/accordion.js +++ /dev/null @@ -1,45 +0,0 @@ -import { createElement, PureComponent } from 'react'; -import { withRouter } from 'react-router'; -import Proptypes from 'prop-types'; -import { connect } from 'react-redux'; -import qs from 'query-string'; -import { getLocationParamUpdated } from 'utils/navigation'; - -import AccordionComponent from './accordion-component'; - -const mapStateToProps = (state, { location, param }) => { - const search = qs.parse(location.search); - const openSlug = search[param] || null; - return { - openSlug - }; -}; - -class AccordionContainer extends PureComponent { - handleOnClick = slug => { - const { param, location } = this.props; - const search = qs.parse(location.search); - const newSlug = search[param] === slug || !search[param] ? 'none' : slug; - this.updateUrlParam({ name: param, value: newSlug }); - }; - - updateUrlParam = (params, clear) => { - const { history, location } = this.props; - history.replace(getLocationParamUpdated(location, params, clear)); - }; - - render() { - return createElement(AccordionComponent, { - handleOnClick: this.handleOnClick, - ...this.props - }); - } -} - -AccordionContainer.propTypes = { - location: Proptypes.object, - history: Proptypes.object, - param: Proptypes.string -}; - -export default withRouter(connect(mapStateToProps, null)(AccordionContainer)); diff --git a/app/javascript/app/components/accordion/accordion-styles.scss b/app/javascript/app/components/accordion/accordion-styles.scss index c8e8c461fa..3b5b288a00 100644 --- a/app/javascript/app/components/accordion/accordion-styles.scss +++ b/app/javascript/app/components/accordion/accordion-styles.scss @@ -37,56 +37,3 @@ } } -.accordionContent { - background-color: $light-gray; - border-bottom: solid 1px rgba($theme-color, 0.3); -} - -.definitionCompare { - @extend %grid; - - @include msGridColumns(1fr, 1fr, 1fr, 1fr); - - grid-template-columns: repeat(4, 1fr); - padding: 30px 0; - border-bottom: solid 1px rgba(#9090b2, 0.15); - - &:last-child { - border-bottom: 0; - } -} - -.definition { - @extend %grid; - - @include msGridColumns(1fr, 3fr); - - grid-template-columns: 1fr 3fr; - padding: 20px 0; - border-bottom: solid 1px rgba(#9090b2, 0.15); - - &:last-child { - border-bottom: 0; - } -} - -.definitionTitle { - margin-right: 40px; - color: $theme-color; - font-weight: $font-weight-bold; - font-family: $font-family-1; -} - -.definitionDesc { - color: $theme-color; - font-family: $font-family-1; - line-height: 1.4em; -} - -.loader { - min-height: 400px; - position: absolute; - top: 0; - left: 0; - right: 0; -} diff --git a/app/javascript/app/components/definition-list/definition-list-component.jsx b/app/javascript/app/components/definition-list/definition-list-component.jsx new file mode 100644 index 0000000000..88d2b477d4 --- /dev/null +++ b/app/javascript/app/components/definition-list/definition-list-component.jsx @@ -0,0 +1,44 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; + +import styles from './definition-list-styles.scss'; + +class DefinitionList extends PureComponent { + // eslint-disable-line react/prefer-stateless-function + render() { + const { definitions, compare, className } = this.props; + return ( +
+ {definitions.map(def => ( +
+
{def.title}
+ {def.descriptions.map(desc => ( +
+
+
+ ))} +
+ ))} +
+ ); + } +} + +DefinitionList.propTypes = { + className: PropTypes.string, + definitions: PropTypes.array, + compare: PropTypes.bool +}; + +export default DefinitionList; diff --git a/app/javascript/app/components/definition-list/definition-list-styles.scss b/app/javascript/app/components/definition-list/definition-list-styles.scss new file mode 100644 index 0000000000..7e8d23e7ca --- /dev/null +++ b/app/javascript/app/components/definition-list/definition-list-styles.scss @@ -0,0 +1,42 @@ +@import '~styles/layout.scss'; + +.definitionCompare { + @extend %grid; + + @include msGridColumns(1fr, 1fr, 1fr, 1fr); + + grid-template-columns: repeat(4, 1fr); + padding: 30px 0; + border-bottom: solid 1px rgba(#9090b2, 0.15); + + &:last-child { + border-bottom: 0; + } +} + +.definition { + @extend %grid; + + @include msGridColumns(1fr, 3fr); + + grid-template-columns: 1fr 3fr; + padding: 20px 0; + border-bottom: solid 1px rgba(#9090b2, 0.15); + + &:last-child { + border-bottom: 0; + } +} + +.definitionTitle { + margin-right: 40px; + color: $theme-color; + font-weight: $font-weight-bold; + font-family: $font-family-1; +} + +.definitionDesc { + color: $theme-color; + font-family: $font-family-1; + line-height: 1.4em; +} \ No newline at end of file diff --git a/app/javascript/app/components/definition-list/definition-list.js b/app/javascript/app/components/definition-list/definition-list.js new file mode 100644 index 0000000000..a75fff6614 --- /dev/null +++ b/app/javascript/app/components/definition-list/definition-list.js @@ -0,0 +1,3 @@ +import Component from './definition-list-component'; + +export default Component; diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx index b1af2df143..a15c3e2159 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx @@ -3,7 +3,9 @@ import PropTypes from 'prop-types'; import Accordion from 'components/accordion'; import NoContent from 'components/no-content'; import Loading from 'components/loading'; +import DefinitionList from 'components/definition-list'; +import layout from 'styles/layout.scss'; import styles from './ndcs-country-accordion-styles.scss'; class NdcsCountryAccordion extends PureComponent { @@ -26,14 +28,21 @@ class NdcsCountryAccordion extends PureComponent { param="section" data={ndcsData} loading={loading} - compare={compare} > -
- test1 -
-
- test2 -
+ {ndcsData && + ndcsData.map(section => ( +
+ +
+ )) + } )} diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-styles.scss b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-styles.scss index ed06f9acc5..48dce77fad 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-styles.scss +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-styles.scss @@ -24,3 +24,8 @@ $min-height: 450px; .noContent { min-height: $min-height; } + +.definitionList { + background-color: $light-gray; + border-bottom: solid 1px rgba($theme-color, 0.3); +} From e2b7554fab36adb230481d2886207242354e715e Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Wed, 1 Nov 2017 20:36:33 +0000 Subject: [PATCH 04/43] Sectoral coverage tab for compare --- .../accordion/accordion-component.jsx | 92 +++++++++++-------- .../accordion/accordion-styles.scss | 7 ++ .../definition-list-component.jsx | 21 +++-- .../definition-list-styles.scss | 5 +- .../ndcs-country-accordion-component.jsx | 57 +++++++++--- .../ndcs-country-accordion-selectors.js | 77 +++++++++++++++- .../ndcs-country-accordion.js | 16 ++-- app/javascript/app/routes.js | 24 +++++ 8 files changed, 229 insertions(+), 70 deletions(-) diff --git a/app/javascript/app/components/accordion/accordion-component.jsx b/app/javascript/app/components/accordion/accordion-component.jsx index 9fb187caf1..8e02b5d886 100644 --- a/app/javascript/app/components/accordion/accordion-component.jsx +++ b/app/javascript/app/components/accordion/accordion-component.jsx @@ -10,48 +10,61 @@ import styles from './accordion-styles.scss'; class Accordion extends PureComponent { render() { - const { className, data, handleOnClick, openSlug, children } = this.props; + const { + className, + data, + handleOnClick, + openSlug, + children, + isChild + } = this.props; return (
- {data.map((section, index) => { - let isOpen = index === 0; - if (openSlug) { - if (openSlug !== 'none') { - const isActiveInResults = data.some(d => d.slug === openSlug); - isOpen = - openSlug === section.slug || - (index === 0 && !isActiveInResults); - } else { - isOpen = false; + {data && + data.length > 0 && + data.map((section, index) => { + let isOpen = index === 0; + if (openSlug) { + if (openSlug !== 'none') { + const isActiveInResults = data.some(d => d.slug === openSlug); + isOpen = + openSlug === section.slug || + (index === 0 && !isActiveInResults); + } else { + isOpen = false; + } } - } - return ( -
-
- - - {React.Children.map(children, (child, i) => { - if (i === index) return child; - return null; - })} - - - ); - })} + + +
+ {React.Children.map(children, (child, i) => { + if (i === index) return child; + return null; + })} + + + ) : null; + })}
); } @@ -65,10 +78,11 @@ Accordion.propTypes = { PropTypes.shape({ title: PropTypes.string, slug: PropTypes.string, - definitions: PropTypes.array.isRequired + definitions: PropTypes.array }) ), - children: PropTypes.node + children: PropTypes.node, + isChild: PropTypes.bool }; Accordion.defaultProps = { diff --git a/app/javascript/app/components/accordion/accordion-styles.scss b/app/javascript/app/components/accordion/accordion-styles.scss index 3b5b288a00..9b870bc7dd 100644 --- a/app/javascript/app/components/accordion/accordion-styles.scss +++ b/app/javascript/app/components/accordion/accordion-styles.scss @@ -26,6 +26,13 @@ } } +.subHeader { + background-color: rgba($light-gray, 0.5); + padding: 10px 0; + padding-left: 15px; + font-size: $font-size-m; +} + .iconArrow { transition: transform 0.25s ease-in-out; position: absolute; diff --git a/app/javascript/app/components/definition-list/definition-list-component.jsx b/app/javascript/app/components/definition-list/definition-list-component.jsx index 88d2b477d4..6aa7e53a69 100644 --- a/app/javascript/app/components/definition-list/definition-list-component.jsx +++ b/app/javascript/app/components/definition-list/definition-list-component.jsx @@ -18,16 +18,17 @@ class DefinitionList extends PureComponent { )} >
{def.title}
- {def.descriptions.map(desc => ( -
-
-
- ))} + {def.descriptions && + def.descriptions.map(desc => ( +
+
+
+ ))} ))} diff --git a/app/javascript/app/components/definition-list/definition-list-styles.scss b/app/javascript/app/components/definition-list/definition-list-styles.scss index 7e8d23e7ca..32d5f62996 100644 --- a/app/javascript/app/components/definition-list/definition-list-styles.scss +++ b/app/javascript/app/components/definition-list/definition-list-styles.scss @@ -6,7 +6,8 @@ @include msGridColumns(1fr, 1fr, 1fr, 1fr); grid-template-columns: repeat(4, 1fr); - padding: 30px 0; + padding: 20px 0; + margin-left: 30px; border-bottom: solid 1px rgba(#9090b2, 0.15); &:last-child { @@ -21,6 +22,7 @@ grid-template-columns: 1fr 3fr; padding: 20px 0; + margin: 0 30px; border-bottom: solid 1px rgba(#9090b2, 0.15); &:last-child { @@ -39,4 +41,5 @@ color: $theme-color; font-family: $font-family-1; line-height: 1.4em; + word-break: break-word; } \ No newline at end of file diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx index a15c3e2159..06e439cba2 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx @@ -11,18 +11,56 @@ import styles from './ndcs-country-accordion-styles.scss'; class NdcsCountryAccordion extends PureComponent { // eslint-disable-line react/prefer-stateless-function render() { - const { ndcsData, loading, compare, locations } = this.props; + const { ndcsData, loading, compare, locations, category } = this.props; return (
{loading && } - {!ndcsData.length && !loading && ( + {!loading && + (!ndcsData || !ndcsData.length) && ( )} - {ndcsData && ndcsData.length > 0 && ( + {ndcsData && ndcsData.length && category === 'sectoral_information' ? ( + + {ndcsData && + ndcsData.length > 0 && + ndcsData.map( + section => + (section.indicators.length > 0 ? ( + + {section.indicators.map(desc => ( +
+ +
+ ))} +
+ ) : null) + )} +
+ ) : ( {ndcsData && ndcsData.map(section => ( -
+
- )) - } + ))} )}
@@ -54,7 +88,8 @@ NdcsCountryAccordion.propTypes = { ndcsData: PropTypes.array, loading: PropTypes.bool, compare: PropTypes.bool, - locations: PropTypes.array + locations: PropTypes.array, + category: PropTypes.string }; export default NdcsCountryAccordion; diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js index 2871253964..6eb5ba9f81 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js @@ -4,12 +4,13 @@ import { deburrUpper } from 'app/utils'; const getCountries = state => state.countries; const getAllIndicators = state => (state.data ? state.data.indicators : {}); const getCategories = state => (state.data ? state.data.categories : {}); +const getSectors = state => (state.data ? state.data.sectors : {}); const getSearch = state => deburrUpper(state.search); export const parseIndicatorsDefs = createSelector( [getAllIndicators, getCategories, getCountries], (indicators, categories, countries) => { - if (!indicators || !categories || !countries) return {}; + if (!indicators || !categories || !countries) return null; const parsedIndicators = {}; Object.keys(categories).forEach(category => { const categoryIndicators = indicators.filter( @@ -32,10 +33,66 @@ export const parseIndicatorsDefs = createSelector( } ); +export const parseIndicatorsDefsWithSectors = createSelector( + [getAllIndicators, getCategories, getCountries, getSectors], + (indicators, categories, countries, sectors) => { + if (!indicators || !categories || !countries || !sectors) return null; + const parsedIndicators = []; + Object.keys(categories).forEach(category => { + const indicatorsWithCategory = indicators.filter( + indicator => indicator.category_ids.indexOf(category) > -1 + ); + const parsedDefinitions = indicatorsWithCategory.map(indicator => { + const sectorIds = []; + let descriptions = []; + Object.keys(indicator.locations).forEach(location => { + indicator.locations[location].forEach(def => + sectorIds.push(def.sector_id) + ); + }); + if (sectorIds && sectorIds.length && sectorIds[0]) { + sectorIds.forEach(sector => { + const descriptionsBySector = countries.map(country => { + const value = indicator.locations[country] + ? indicator.locations[country].find( + indicValue => indicValue.sector_id === sector + ) + : ''; + return { + iso: country, + value: value ? value.value : null + }; + }); + descriptions.push({ + title: sectors[sector].name, + slug: sector, + descriptions: descriptionsBySector + }); + }); + } else { + descriptions = countries.map(country => ({ + iso: country, + values: indicator.locations[country] + ? indicator.locations[country] + : null + })); + } + return { + title: indicator.name, + slug: indicator.slug, + descriptions + }; + }); + parsedIndicators[category] = parsedDefinitions; + }); + return parsedIndicators; + } +); + export const getNDCs = createSelector( [getCategories, parseIndicatorsDefs], (categories, indicators) => { - if (!categories) return []; + if (!categories) return null; const ndcs = Object.keys(categories).map(category => ({ title: categories[category].name, slug: categories[category].slug, @@ -45,6 +102,19 @@ export const getNDCs = createSelector( } ); +export const getSectoralNDCs = createSelector( + [getCategories, parseIndicatorsDefsWithSectors], + (categories, indicators) => { + if (!categories || !indicators) return []; + const ndcs = Object.keys(categories).map(category => ({ + title: categories[category].name, + slug: categories[category].slug, + indicators: indicators[category] ? indicators[category] : [] + })); + return ndcs; + } +); + export const filterNDCs = createSelector( [getNDCs, getSearch], (ndcs, search) => { @@ -67,5 +137,6 @@ export const filterNDCs = createSelector( ); export default { - filterNDCs + filterNDCs, + getSectoralNDCs }; diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js index 4d2d60f958..5ab8b70181 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js @@ -8,23 +8,27 @@ import actions from './ndcs-country-accordion-actions'; import reducers, { initialState } from './ndcs-country-accordion-reducers'; import NdcsCountryAccordionComponent from './ndcs-country-accordion-component'; -import { filterNDCs } from './ndcs-country-accordion-selectors'; +import { + filterNDCs, + getSectoralNDCs +} from './ndcs-country-accordion-selectors'; -const mapStateToProps = (state, { match, location }) => { +const mapStateToProps = (state, { match, location, category }) => { const { iso } = match.params; const search = qs.parse(location.search); const locations = search.locations ? search.locations.split(',') : null; const ndcsData = { data: state.ndcCountryAccordion.data, search: search.search, - countries: match.params.iso - ? [match.params.iso] - : locations + countries: match.params.iso ? [match.params.iso] : locations }; return { loading: state.ndcCountryAccordion.loading, + ndcsData: + category === 'sectoral_information' + ? getSectoralNDCs(ndcsData) + : filterNDCs(ndcsData), search, - ndcsData: filterNDCs(ndcsData), iso, locations }; diff --git a/app/javascript/app/routes.js b/app/javascript/app/routes.js index 0f840dc1bb..499226ddf8 100644 --- a/app/javascript/app/routes.js +++ b/app/javascript/app/routes.js @@ -102,6 +102,18 @@ export default [ label: 'Adaptation', param: 'adaptation' }, + { + path: '/ndcs/country/:iso/sectoral-information', + component: () => + createElement(NDCCountryAccordion, { + category: 'sectoral_information', + type: 'overview' + }), + exact: true, + anchor: true, + label: 'Sectoral Information', + param: 'sectoral-information' + }, { path: '/ndcs/country/:iso', component: () => createElement(Redirect, { to: '/ndcs' }) @@ -137,6 +149,18 @@ export default [ label: 'Adaptation', param: 'adaptation' }, + { + path: '/ndcs/compare/sectoral-information', + component: () => + createElement(NDCCountryAccordion, { + category: 'sectoral_information', + compare: true + }), + exact: true, + anchor: true, + label: 'Sectoral Information', + param: 'sectoral-information' + }, { path: '/ndcs/compare', component: () => From 504bc29dbade2ca770b70f00dadefa1f812d1d7c Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Wed, 1 Nov 2017 21:03:20 +0000 Subject: [PATCH 05/43] fix: show loading correctly --- .../definition-list-component.jsx | 4 +- .../ndcs-country-accordion-component.jsx | 107 +++++++++--------- 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/app/javascript/app/components/definition-list/definition-list-component.jsx b/app/javascript/app/components/definition-list/definition-list-component.jsx index 6aa7e53a69..77482ecf39 100644 --- a/app/javascript/app/components/definition-list/definition-list-component.jsx +++ b/app/javascript/app/components/definition-list/definition-list-component.jsx @@ -10,9 +10,9 @@ class DefinitionList extends PureComponent { const { definitions, compare, className } = this.props; return (
- {definitions.map(def => ( + {definitions && definitions.length && definitions.map(def => (
{loading && } - {!loading && - (!ndcsData || !ndcsData.length) && ( + {!loading && (!ndcsData || !ndcsData.length) && ( )} - {ndcsData && ndcsData.length && category === 'sectoral_information' ? ( - - {ndcsData && - ndcsData.length > 0 && - ndcsData.map( - section => - (section.indicators.length > 0 ? ( - - {section.indicators.map(desc => ( -
- -
- ))} -
- ) : null) - )} -
- ) : ( - - {ndcsData && - ndcsData.map(section => ( -
- -
- ))} -
- )} + {!loading && ndcsData && ndcsData.length && +
+ {ndcsData && ndcsData.length && category === 'sectoral_information' ? ( + + {ndcsData && + ndcsData.length > 0 && + ndcsData.map( + section => + (section.indicators.length > 0 ? ( + + {section.indicators.map(desc => ( +
+ +
+ ))} +
+ ) : null) + )} +
+ ) : ( + + {ndcsData && + ndcsData.map(section => ( +
+ +
+ ))} +
+ )} +
+ }
); } From 619e9af503ff546c3c2ecef08e657ad2e9bebda9 Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Wed, 1 Nov 2017 21:04:57 +0000 Subject: [PATCH 06/43] remove 0 --- .../ndcs-country-accordion/ndcs-country-accordion-component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx index 5a77bb2f0d..7ec7965d26 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-component.jsx @@ -28,7 +28,7 @@ class NdcsCountryAccordion extends PureComponent { className={styles.noContent} /> )} - {!loading && ndcsData && ndcsData.length && + {!loading && ndcsData && ndcsData.length > 0 &&
{ndcsData && ndcsData.length && category === 'sectoral_information' ? ( Date: Thu, 2 Nov 2017 11:43:38 +0100 Subject: [PATCH 07/43] Re implement search for sectoral coverage --- .../accordion/accordion-styles.scss | 3 +- .../definition-list-component.jsx | 44 ++++++++++--------- .../ndcs-country-accordion-selectors.js | 38 ++++++++++++++-- .../ndcs-country-accordion.js | 4 +- 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/app/javascript/app/components/accordion/accordion-styles.scss b/app/javascript/app/components/accordion/accordion-styles.scss index 9b870bc7dd..006b74a57f 100644 --- a/app/javascript/app/components/accordion/accordion-styles.scss +++ b/app/javascript/app/components/accordion/accordion-styles.scss @@ -28,7 +28,8 @@ .subHeader { background-color: rgba($light-gray, 0.5); - padding: 10px 0; + padding-top: 10px; + padding-bottom: 10px; padding-left: 15px; font-size: $font-size-m; } diff --git a/app/javascript/app/components/definition-list/definition-list-component.jsx b/app/javascript/app/components/definition-list/definition-list-component.jsx index 77482ecf39..de837927e8 100644 --- a/app/javascript/app/components/definition-list/definition-list-component.jsx +++ b/app/javascript/app/components/definition-list/definition-list-component.jsx @@ -10,27 +10,29 @@ class DefinitionList extends PureComponent { const { definitions, compare, className } = this.props; return (
- {definitions && definitions.length && definitions.map(def => ( -
-
{def.title}
- {def.descriptions && - def.descriptions.map(desc => ( -
-
-
- ))} -
- ))} + {definitions && + definitions.length > 0 && + definitions.map(def => ( +
+
{def.title}
+ {def.descriptions && + def.descriptions.map(desc => ( +
+
+
+ ))} +
+ ))}
); } diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js index 6eb5ba9f81..56671f9196 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js @@ -105,7 +105,7 @@ export const getNDCs = createSelector( export const getSectoralNDCs = createSelector( [getCategories, parseIndicatorsDefsWithSectors], (categories, indicators) => { - if (!categories || !indicators) return []; + if (!categories || !indicators) return null; const ndcs = Object.keys(categories).map(category => ({ title: categories[category].name, slug: categories[category].slug, @@ -118,7 +118,7 @@ export const getSectoralNDCs = createSelector( export const filterNDCs = createSelector( [getNDCs, getSearch], (ndcs, search) => { - if (!ndcs) return []; + if (!ndcs) return null; const filteredNDCs = ndcs.map(ndc => { const defs = ndc.definitions.filter( def => @@ -136,7 +136,39 @@ export const filterNDCs = createSelector( } ); +export const filterSectoralNDCs = createSelector( + [getSectoralNDCs, getSearch], + (ndcs, search) => { + if (!ndcs) return null; + const reducedNDCs = ndcs.filter( + ndc => ndc.indicators && ndc.indicators.length + ); + const filteredNDCs = reducedNDCs.map(ndc => { + const defs = []; + ndc.indicators.forEach(def => { + const descs = def.descriptions.filter( + desc => + deburrUpper(desc.title).indexOf(search) > -1 || + deburrUpper(desc.descriptions[0].value).indexOf(search) > -1 + ); + if (descs && descs.length) { + defs.push({ + ...def, + descriptions: descs + }); + } + }); + + return { + ...ndc, + indicators: defs + }; + }); + return filteredNDCs; + } +); + export default { filterNDCs, - getSectoralNDCs + filterSectoralNDCs }; diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js index 5ab8b70181..a266533d3c 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js @@ -10,7 +10,7 @@ import reducers, { initialState } from './ndcs-country-accordion-reducers'; import NdcsCountryAccordionComponent from './ndcs-country-accordion-component'; import { filterNDCs, - getSectoralNDCs + filterSectoralNDCs } from './ndcs-country-accordion-selectors'; const mapStateToProps = (state, { match, location, category }) => { @@ -26,7 +26,7 @@ const mapStateToProps = (state, { match, location, category }) => { loading: state.ndcCountryAccordion.loading, ndcsData: category === 'sectoral_information' - ? getSectoralNDCs(ndcsData) + ? filterSectoralNDCs(ndcsData) : filterNDCs(ndcsData), search, iso, From edfba182ee2fc21c4338ed0a0ed1c9bd5abdeb2a Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 11:58:48 +0100 Subject: [PATCH 08/43] fix: position of open icon --- .../app/components/accordion/accordion-styles.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/javascript/app/components/accordion/accordion-styles.scss b/app/javascript/app/components/accordion/accordion-styles.scss index 006b74a57f..b42f12f8d4 100644 --- a/app/javascript/app/components/accordion/accordion-styles.scss +++ b/app/javascript/app/components/accordion/accordion-styles.scss @@ -30,8 +30,11 @@ background-color: rgba($light-gray, 0.5); padding-top: 10px; padding-bottom: 10px; - padding-left: 15px; font-size: $font-size-m; + + .title { + padding-left: 15px; + } } .iconArrow { From ff33502545bfcc7dd75f0324074635bf3ada62e5 Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 12:12:42 +0100 Subject: [PATCH 09/43] Reduce accordion logic --- .../app/components/accordion/accordion-component.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/app/components/accordion/accordion-component.jsx b/app/javascript/app/components/accordion/accordion-component.jsx index 8e02b5d886..d849d2493b 100644 --- a/app/javascript/app/components/accordion/accordion-component.jsx +++ b/app/javascript/app/components/accordion/accordion-component.jsx @@ -34,7 +34,7 @@ class Accordion extends PureComponent { isOpen = false; } } - return children[index] ? ( + return (
- ) : null; + ); })}
); From a274f9912fe67bc761bc3744f90afccac921ad7c Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 12:21:25 +0100 Subject: [PATCH 10/43] add overview filter to fetch --- .../ndcs-country-accordion-actions.js | 6 ++++-- .../ndcs-country-accordion.js | 17 ++++++++++++----- app/javascript/app/routes.js | 9 +++------ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-actions.js b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-actions.js index c689453426..a8c9a0e776 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-actions.js +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-actions.js @@ -13,11 +13,13 @@ const fetchNdcsCountryAccordionFailed = createAction( const fetchNdcsCountryAccordion = createThunkAction( 'fetchNdcsCountryAccordion', - (locations, category) => dispatch => { + (locations, category, compare) => dispatch => { if (locations) { dispatch(fetchNdcsCountryAccordionInit()); fetch( - `/api/v1/ndcs?location=${locations}&category=${category}&filter=overview` + `/api/v1/ndcs?location=${locations}&category=${category}${!compare + ? '&filter=overview' + : ''}` ) .then(response => { if (response.ok) return response.json(); diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js index a266533d3c..e7eef91903 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion.js @@ -36,17 +36,23 @@ const mapStateToProps = (state, { match, location, category }) => { class NdcsCountryAccordionContainer extends PureComponent { componentWillMount() { - const { iso, fetchNdcsCountryAccordion, category, search } = this.props; + const { + iso, + fetchNdcsCountryAccordion, + category, + search, + compare + } = this.props; const locations = iso || search.locations; - fetchNdcsCountryAccordion(locations, category); + fetchNdcsCountryAccordion(locations, category, compare); } componentWillReceiveProps(nextProps) { - const { fetchNdcsCountryAccordion } = this.props; + const { fetchNdcsCountryAccordion, compare } = this.props; const newLocations = qs.parse(nextProps.location.search).locations; const oldLocations = qs.parse(this.props.location.search).locations; if (newLocations !== oldLocations) { - fetchNdcsCountryAccordion(newLocations, nextProps.category); + fetchNdcsCountryAccordion(newLocations, nextProps.category, compare); } } @@ -62,7 +68,8 @@ NdcsCountryAccordionContainer.propTypes = { iso: PropTypes.string, category: PropTypes.string, search: PropTypes.object, - location: PropTypes.object + location: PropTypes.object, + compare: PropTypes.bool }; export { actions, reducers, initialState }; diff --git a/app/javascript/app/routes.js b/app/javascript/app/routes.js index f3eaa28ae1..5bec642ae0 100644 --- a/app/javascript/app/routes.js +++ b/app/javascript/app/routes.js @@ -82,8 +82,7 @@ export default [ path: '/ndcs/country/:iso/mitigation', component: () => createElement(NDCCountryAccordion, { - category: 'mitigation', - type: 'overview' + category: 'mitigation' }), exact: true, anchor: true, @@ -94,8 +93,7 @@ export default [ path: '/ndcs/country/:iso/adaptation', component: () => createElement(NDCCountryAccordion, { - category: 'adaptation', - type: 'overview' + category: 'adaptation' }), exact: true, anchor: true, @@ -106,8 +104,7 @@ export default [ path: '/ndcs/country/:iso/sectoral-information', component: () => createElement(NDCCountryAccordion, { - category: 'sectoral_information', - type: 'overview' + category: 'sectoral_information' }), exact: true, anchor: true, From f466761b3252339196853e5ee3cad821e2b56c61 Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 12:25:55 +0100 Subject: [PATCH 11/43] clean compare page component and selectors --- .../pages/ndc-compare/ndc-compare-actions.js | 36 ---------------- .../pages/ndc-compare/ndc-compare-reducers.js | 27 ------------ .../ndc-compare/ndc-compare-selectors.js | 42 ------------------- .../app/pages/ndc-compare/ndc-compare.js | 15 +------ app/javascript/app/reducers.js | 2 - 5 files changed, 1 insertion(+), 121 deletions(-) delete mode 100644 app/javascript/app/pages/ndc-compare/ndc-compare-actions.js delete mode 100644 app/javascript/app/pages/ndc-compare/ndc-compare-reducers.js diff --git a/app/javascript/app/pages/ndc-compare/ndc-compare-actions.js b/app/javascript/app/pages/ndc-compare/ndc-compare-actions.js deleted file mode 100644 index 78486efe60..0000000000 --- a/app/javascript/app/pages/ndc-compare/ndc-compare-actions.js +++ /dev/null @@ -1,36 +0,0 @@ -import { createAction } from 'redux-actions'; -import { createThunkAction } from 'utils/redux'; - -/* @tmpfix: remove usage of indcTransform */ -import indcTransform from 'utils/indctransform'; - -const fetchCompareNDCInit = createAction('fetchCompareNDCInit'); -const fetchCompareNDCReady = createAction('fetchCompareNDCReady'); -const fetchCompareNDCFailed = createAction('fetchCompareNDCFailed'); - -const fetchCompareNDC = createThunkAction( - 'fetchCompareNDC', - isos => dispatch => { - dispatch(fetchCompareNDCInit()); - fetch(`/api/v1/ndcs?location=${isos}`) - .then(response => { - if (response.ok) return response.json(); - throw Error(response.statusText); - }) - .then(data => indcTransform(data)) - .then(data => { - dispatch(fetchCompareNDCReady(data)); - }) - .catch(error => { - dispatch(fetchCompareNDCFailed()); - console.info(error); - }); - } -); - -export default { - fetchCompareNDC, - fetchCompareNDCInit, - fetchCompareNDCReady, - fetchCompareNDCFailed -}; diff --git a/app/javascript/app/pages/ndc-compare/ndc-compare-reducers.js b/app/javascript/app/pages/ndc-compare/ndc-compare-reducers.js deleted file mode 100644 index a8a5e1e210..0000000000 --- a/app/javascript/app/pages/ndc-compare/ndc-compare-reducers.js +++ /dev/null @@ -1,27 +0,0 @@ -export const initialState = { - loading: false, - loaded: false, - data: {} -}; - -const setLoading = (loading, state) => ({ ...state, loading }); -const setLoaded = (loaded, state) => ({ ...state, loaded }); - -export default { - fetchCompareNDCInit: state => setLoading(true, state), - fetchCompareNDCReady: (state, { payload }) => { - const data = { ...state, data: payload }; - - return setLoaded(true, setLoading(false, data)); - }, - fetchCompareNDCFailed: state => { - const data = { - ...state, - data: { - fetched: true - } - }; - - return setLoaded(true, setLoading(false, data)); - } -}; diff --git a/app/javascript/app/pages/ndc-compare/ndc-compare-selectors.js b/app/javascript/app/pages/ndc-compare/ndc-compare-selectors.js index bd8838e54f..999961afb5 100644 --- a/app/javascript/app/pages/ndc-compare/ndc-compare-selectors.js +++ b/app/javascript/app/pages/ndc-compare/ndc-compare-selectors.js @@ -4,8 +4,6 @@ import compact from 'lodash/compact'; const getCountries = state => state.data || null; const getLocations = state => state.locations || null; const getIso = state => state.iso; -const getAllIndicators = state => state.data.indicators || null; -const getCategories = state => state.data.categories || null; const getCountryByIso = (countries, iso) => countries.find(country => country.iso_code3 === iso); @@ -47,45 +45,6 @@ export const getCountriesOptionsFiltered = createSelector( } ); -export const parseIndicatorsDefs = createSelector( - [getAllIndicators, getCategories, getLocations], - (indicators, categories, countries) => { - if (!indicators || !categories || !countries) return {}; - const parsedIndicators = {}; - Object.keys(categories).forEach(category => { - const categoryIndicators = indicators.filter( - indicator => indicator.category_ids.indexOf(category) > -1 - ); - const parsedDefinitions = categoryIndicators.map(def => { - const descriptions = countries.map(country => ({ - iso: country, - value: def.locations[country] ? def.locations[country].value : null - })); - return { - title: def.name, - slug: def.slug, - descriptions - }; - }); - parsedIndicators[category] = parsedDefinitions; - }); - return parsedIndicators; - } -); - -export const getNDCs = createSelector( - [getCategories, parseIndicatorsDefs], - (categories, indicators) => { - if (!categories) return []; - const ndcs = Object.keys(categories).map(category => ({ - title: categories[category].name, - slug: categories[category].slug, - definitions: indicators[category] ? indicators[category] : [] - })); - return ndcs; - } -); - export const getCountry = createSelector( [getCountries, getIso], getCountryByIso @@ -106,6 +65,5 @@ export default { getCountriesOptions, getCountriesOptionsFiltered, getActiveCountries, - getNDCs, getAnchorLinks }; diff --git a/app/javascript/app/pages/ndc-compare/ndc-compare.js b/app/javascript/app/pages/ndc-compare/ndc-compare.js index 9259c1018e..9a84815695 100644 --- a/app/javascript/app/pages/ndc-compare/ndc-compare.js +++ b/app/javascript/app/pages/ndc-compare/ndc-compare.js @@ -6,12 +6,8 @@ import qs from 'query-string'; import isEmpty from 'lodash/isEmpty'; import { getLocationParamUpdated } from 'utils/navigation'; -import actions from './ndc-compare-actions'; -import reducers, { initialState } from './ndc-compare-reducers'; - import NDCCompareComponent from './ndc-compare-component'; import { - getNDCs, getCountriesOptionsFiltered, getActiveCountries, getAnchorLinks @@ -20,10 +16,6 @@ import { const mapStateToProps = (state, { location, route }) => { const search = qs.parse(location.search); const locations = search.locations ? search.locations.split(',') : []; - const ndcsData = { - data: state.NDCCompare.data, - locations - }; const activeCountriesData = { data: state.countries.data, locations @@ -39,7 +31,6 @@ const mapStateToProps = (state, { location, route }) => { return { fetched: !isEmpty(state.NDCCompare.data), loading: state.NDCCompare.loading, - ndcsData: getNDCs(ndcsData), locations, countriesOptions: getCountriesOptionsFiltered(countriesOptionsData), activeCountriesOptions: getActiveCountries(activeCountriesData), @@ -74,8 +65,4 @@ NDCCompareContainer.propTypes = { locations: Proptypes.array }; -export { actions, reducers, initialState }; - -export default withRouter( - connect(mapStateToProps, actions)(NDCCompareContainer) -); +export default withRouter(connect(mapStateToProps, null)(NDCCompareContainer)); diff --git a/app/javascript/app/reducers.js b/app/javascript/app/reducers.js index c907630831..75db3b0e46 100644 --- a/app/javascript/app/reducers.js +++ b/app/javascript/app/reducers.js @@ -31,14 +31,12 @@ const providersReducers = { import * as NDCSPage from 'pages/ndcs'; import * as countryNDCFullPage from 'pages/ndc-country-full'; -import * as NDCComparePage from 'pages/ndc-compare'; import * as ndcSearchPage from 'pages/ndc-search'; import * as ndcSdgPage from 'pages/ndc-sdg'; const pagesReducers = { ndcs: handleActions(NDCSPage), countryNDCFull: handleActions(countryNDCFullPage), - NDCCompare: handleActions(NDCComparePage), ndcSearch: handleActions(ndcSearchPage), ndcSdg: handleActions(ndcSdgPage) }; From 40eaf0539001c0b8a5b8ba21a99330a04f896fd5 Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 13:25:43 +0100 Subject: [PATCH 12/43] add provider for NDCs documents meta and add selector to navigate from /country, tidy up reducers --- .../app/pages/ndc-compare/ndc-compare.js | 3 -- .../pages/ndc-country/ndc-country-actions.js | 36 ------------------ .../ndc-country/ndc-country-component.jsx | 38 ++++++++++++------- .../pages/ndc-country/ndc-country-reducers.js | 34 ----------------- .../ndc-country/ndc-country-selectors.js | 22 +++++++++-- .../pages/ndc-country/ndc-country-styles.scss | 8 ++-- .../app/pages/ndc-country/ndc-country.js | 29 ++++++++------ .../ndcs-documents-meta-provider-actions.js | 34 +++++++++++++++++ .../ndcs-documents-meta-provider-reducers.js | 22 +++++++++++ .../ndcs-documents-meta-provider.js | 27 +++++++++++++ app/javascript/app/reducers.js | 4 +- 11 files changed, 151 insertions(+), 106 deletions(-) delete mode 100644 app/javascript/app/pages/ndc-country/ndc-country-actions.js delete mode 100644 app/javascript/app/pages/ndc-country/ndc-country-reducers.js create mode 100644 app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider-actions.js create mode 100644 app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider-reducers.js create mode 100644 app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider.js diff --git a/app/javascript/app/pages/ndc-compare/ndc-compare.js b/app/javascript/app/pages/ndc-compare/ndc-compare.js index 9a84815695..f28f27b421 100644 --- a/app/javascript/app/pages/ndc-compare/ndc-compare.js +++ b/app/javascript/app/pages/ndc-compare/ndc-compare.js @@ -3,7 +3,6 @@ import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import Proptypes from 'prop-types'; import qs from 'query-string'; -import isEmpty from 'lodash/isEmpty'; import { getLocationParamUpdated } from 'utils/navigation'; import NDCCompareComponent from './ndc-compare-component'; @@ -29,8 +28,6 @@ const mapStateToProps = (state, { location, route }) => { route }; return { - fetched: !isEmpty(state.NDCCompare.data), - loading: state.NDCCompare.loading, locations, countriesOptions: getCountriesOptionsFiltered(countriesOptionsData), activeCountriesOptions: getActiveCountries(activeCountriesData), diff --git a/app/javascript/app/pages/ndc-country/ndc-country-actions.js b/app/javascript/app/pages/ndc-country/ndc-country-actions.js deleted file mode 100644 index 52802273a8..0000000000 --- a/app/javascript/app/pages/ndc-country/ndc-country-actions.js +++ /dev/null @@ -1,36 +0,0 @@ -import { createAction } from 'redux-actions'; -import { createThunkAction } from 'utils/redux'; - -const fetchCountryNDCInit = createAction('fetchCountryNDCInit'); -const fetchCountryNDCReady = createAction('fetchCountryNDCReady'); -const fetchCountryNDCFailed = createAction('fetchCountryNDCFailed'); - -const fetchCountryNDC = createThunkAction( - 'fetchCountryNDC', - iso => dispatch => { - dispatch(fetchCountryNDCInit()); - fetch(`/api/v1/ndcs?location=${iso}&filter=overview`) - .then(response => { - if (response.ok) return response.json(); - throw Error(response.statusText); - }) - .then(data => { - const dataWithIso = { - iso, - data - }; - dispatch(fetchCountryNDCReady(dataWithIso)); - }) - .catch(error => { - dispatch(fetchCountryNDCFailed(iso)); - console.info(error); - }); - } -); - -export default { - fetchCountryNDC, - fetchCountryNDCInit, - fetchCountryNDCReady, - fetchCountryNDCFailed -}; diff --git a/app/javascript/app/pages/ndc-country/ndc-country-component.jsx b/app/javascript/app/pages/ndc-country/ndc-country-component.jsx index 3222686340..7c00a075fd 100644 --- a/app/javascript/app/pages/ndc-country/ndc-country-component.jsx +++ b/app/javascript/app/pages/ndc-country/ndc-country-component.jsx @@ -8,7 +8,10 @@ import Search from 'components/search'; import cx from 'classnames'; import Sticky from 'react-stickynode'; import AnchorNav from 'components/anchor-nav'; +import NdcsDocumentsMetaProvider from 'providers/ndcs-documents-meta-provider'; +import Dropdown from 'components/dropdown'; +import theme from 'styles/themes/dropdown/dropdown-links.scss'; import lightSearch from 'styles/themes/search/search-light.scss'; import layout from 'styles/layout.scss'; import styles from './ndc-country-styles.scss'; @@ -21,10 +24,13 @@ class NDCCountry extends PureComponent { onSearchChange, search, route, - anchorLinks + anchorLinks, + documentsOptions, + handleDropDownChange } = this.props; return (
+ {country && (
- + + -
@@ -76,7 +84,9 @@ NDCCountry.propTypes = { country: PropTypes.object, onSearchChange: PropTypes.func.isRequired, search: PropTypes.string, - anchorLinks: PropTypes.array + anchorLinks: PropTypes.array, + documentsOptions: PropTypes.array, + handleDropDownChange: PropTypes.func }; export default NDCCountry; diff --git a/app/javascript/app/pages/ndc-country/ndc-country-reducers.js b/app/javascript/app/pages/ndc-country/ndc-country-reducers.js deleted file mode 100644 index c6620781e9..0000000000 --- a/app/javascript/app/pages/ndc-country/ndc-country-reducers.js +++ /dev/null @@ -1,34 +0,0 @@ -export const initialState = { - loading: false, - loaded: false, - data: {} -}; - -const setLoading = (loading, state) => ({ ...state, loading }); -const setLoaded = (loaded, state) => ({ ...state, loaded }); - -export default { - fetchCountryNDCInit: state => setLoading(true, state), - fetchCountryNDCReady: (state, { payload }) => { - const newState = { - ...state, - data: { - ...state.data, - [payload.iso]: payload.data - } - }; - - return setLoaded(true, setLoading(false, newState)); - }, - fetchCountryNDCFailed: (state, { payload }) => { - const newState = { - ...state, - data: { - ...state.data, - [payload]: [] - } - }; - - return setLoaded(true, setLoading(false, newState)); - } -}; diff --git a/app/javascript/app/pages/ndc-country/ndc-country-selectors.js b/app/javascript/app/pages/ndc-country/ndc-country-selectors.js index d541ac72ee..5ee88c639b 100644 --- a/app/javascript/app/pages/ndc-country/ndc-country-selectors.js +++ b/app/javascript/app/pages/ndc-country/ndc-country-selectors.js @@ -1,7 +1,10 @@ import { createSelector } from 'reselect'; +import isEmpty from 'lodash/isEmpty'; +import upperCase from 'lodash/upperCase'; -const getCountries = state => state.countries; -const getIso = state => state.iso; +const getCountries = state => state.countries || null; +const getIso = state => state.iso || null; +const getDocuments = state => state.data || null; const getCountryByIso = (countries, iso) => countries.find(country => country.iso_code3 === iso); @@ -25,7 +28,20 @@ export const getAnchorLinks = createSelector( })) ); +export const getDocumentsOptions = createSelector( + [getDocuments, getIso], + (documents, iso) => { + if (isEmpty(documents) || !iso) return null; + return documents[iso].map(doc => ({ + label: `${upperCase(doc.document_type)}(${doc.language})`, + value: `${doc.document_type}(${doc.language})`, + path: `/ndcs/country/${iso}/full?document=${doc.document_type}-${doc.language}` + })); + } +); + export default { getCountry, - getAnchorLinks + getAnchorLinks, + getDocumentsOptions }; diff --git a/app/javascript/app/pages/ndc-country/ndc-country-styles.scss b/app/javascript/app/pages/ndc-country/ndc-country-styles.scss index 0212190467..e6eb961428 100644 --- a/app/javascript/app/pages/ndc-country/ndc-country-styles.scss +++ b/app/javascript/app/pages/ndc-country/ndc-country-styles.scss @@ -3,17 +3,17 @@ .doubleFold { @extend %grid; - @include msGridColumns(1fr, 1fr); + @include msGridColumns(5fr, 7fr); - grid-template-columns: repeat(2, 1fr); + grid-template-columns: 5fr 7fr; } .threeFold { @extend %grid; - @include msGridColumns(1fr, 1fr, 1fr); + @include msGridColumns(3fr, 2fr, 2fr); - grid-template-columns: repeat(3, 1fr); + grid-template-columns: 3fr 2fr 2fr; } .backButton { diff --git a/app/javascript/app/pages/ndc-country/ndc-country.js b/app/javascript/app/pages/ndc-country/ndc-country.js index 6459b7094b..57c40f8a76 100644 --- a/app/javascript/app/pages/ndc-country/ndc-country.js +++ b/app/javascript/app/pages/ndc-country/ndc-country.js @@ -5,11 +5,12 @@ import { withRouter } from 'react-router-dom'; import qs from 'query-string'; import { getLocationParamUpdated } from 'utils/navigation'; -import actions from './ndc-country-actions'; -import reducers, { initialState } from './ndc-country-reducers'; - import NDCCountryComponent from './ndc-country-component'; -import { getCountry, getAnchorLinks } from './ndc-country-selectors'; +import { + getCountry, + getAnchorLinks, + getDocumentsOptions +} from './ndc-country-selectors'; const mapStateToProps = (state, { match, location, route }) => { const { iso } = match.params; @@ -23,10 +24,15 @@ const mapStateToProps = (state, { match, location, route }) => { location, route }; + const documentsData = { + iso, + data: state.ndcsDocumentsMeta.data + }; return { country: getCountry(countryData), search: search.search, - anchorLinks: getAnchorLinks(routeData) + anchorLinks: getAnchorLinks(routeData), + documentsOptions: getDocumentsOptions(documentsData) }; }; @@ -40,10 +46,15 @@ class NDCCountryContainer extends PureComponent { history.replace(getLocationParamUpdated(location, params, clear)); }; + handleDropDownChange = selected => { + this.props.history.push(selected.path); + }; + render() { return createElement(NDCCountryComponent, { ...this.props, - onSearchChange: this.onSearchChange + onSearchChange: this.onSearchChange, + handleDropDownChange: this.handleDropDownChange }); } } @@ -53,8 +64,4 @@ NDCCountryContainer.propTypes = { location: Proptypes.object.isRequired }; -export { actions, reducers, initialState }; - -export default withRouter( - connect(mapStateToProps, actions)(NDCCountryContainer) -); +export default withRouter(connect(mapStateToProps, null)(NDCCountryContainer)); diff --git a/app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider-actions.js b/app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider-actions.js new file mode 100644 index 0000000000..ee3709fb69 --- /dev/null +++ b/app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider-actions.js @@ -0,0 +1,34 @@ +import { createAction } from 'redux-actions'; +import { createThunkAction } from 'utils/redux'; + +const fetchNdcsDocumentsMetaInit = createAction('fetchNdcsDocumentsMetaInit'); +const fetchNdcsDocumentsMetaReady = createAction('fetchNdcsDocumentsMetaReady'); +const fetchNdcsDocumentsMetaFailed = createAction( + 'fetchNdcsDocumentsMetaFailed' +); + +const fetchNdcsDocumentsMeta = createThunkAction( + 'fetchNdcsDocumentsMeta', + () => dispatch => { + dispatch(fetchNdcsDocumentsMetaInit()); + fetch('/api/v1/ndcs/text') + .then(response => { + if (response.ok) return response.json(); + throw Error(response.statusText); + }) + .then(data => { + dispatch(fetchNdcsDocumentsMetaReady(data)); + }) + .catch(error => { + dispatch(fetchNdcsDocumentsMetaFailed()); + console.info(error); + }); + } +); + +export default { + fetchNdcsDocumentsMeta, + fetchNdcsDocumentsMetaInit, + fetchNdcsDocumentsMetaReady, + fetchNdcsDocumentsMetaFailed +}; diff --git a/app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider-reducers.js b/app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider-reducers.js new file mode 100644 index 0000000000..e90badff07 --- /dev/null +++ b/app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider-reducers.js @@ -0,0 +1,22 @@ +import groupBy from 'lodash/groupBy'; + +export const initialState = { + loading: false, + loaded: false, + data: {} +}; + +const setLoading = (loading, state) => ({ ...state, loading }); +const setLoaded = (loaded, state) => ({ ...state, loaded }); + +export default { + fetchNdcsDocumentsMetaInit: state => setLoading(true, state), + fetchNdcsDocumentsMetaReady: (state, { payload }) => { + const newState = { + ...state, + data: groupBy(payload, 'location.iso_code3') + }; + + return setLoaded(true, setLoading(false, newState)); + } +}; diff --git a/app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider.js b/app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider.js new file mode 100644 index 0000000000..da8c53aba8 --- /dev/null +++ b/app/javascript/app/providers/ndcs-documents-meta-provider/ndcs-documents-meta-provider.js @@ -0,0 +1,27 @@ +import { PureComponent } from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { withRouter } from 'react-router'; + +import actions from './ndcs-documents-meta-provider-actions'; +import reducers, { + initialState +} from './ndcs-documents-meta-provider-reducers'; + +class NdcsDocumentsMetaProvider extends PureComponent { + componentDidMount() { + const { fetchNdcsDocumentsMeta } = this.props; + fetchNdcsDocumentsMeta(); + } + + render() { + return null; + } +} + +NdcsDocumentsMetaProvider.propTypes = { + fetchNdcsDocumentsMeta: PropTypes.func.isRequired +}; + +export { actions, reducers, initialState }; +export default withRouter(connect(null, actions)(NdcsDocumentsMetaProvider)); diff --git a/app/javascript/app/reducers.js b/app/javascript/app/reducers.js index 75db3b0e46..6c49dbe2a2 100644 --- a/app/javascript/app/reducers.js +++ b/app/javascript/app/reducers.js @@ -13,6 +13,7 @@ import * as geoLocationProvider from 'providers/geolocation-provider'; import * as wbCountryProvider from 'providers/wb-country-data-provider'; import * as timelineProvider from 'providers/timeline-provider'; import * as socioeconomicsProvider from 'providers/socioeconomics-provider'; +import * as ndcsDocumentsMetaProvider from 'providers/ndcs-documents-meta-provider'; const providersReducers = { countries: handleActions(countriesProvider), @@ -24,7 +25,8 @@ const providersReducers = { geoLocation: handleActions(geoLocationProvider), wbCountryData: handleActions(wbCountryProvider), socioeconomics: handleActions(socioeconomicsProvider), - timeline: handleActions(timelineProvider) + timeline: handleActions(timelineProvider), + ndcsDocumentsMeta: handleActions(ndcsDocumentsMetaProvider) }; // Pages From d248430a632e646a45b581fbbc7b2813bed14e2a Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 13:33:54 +0100 Subject: [PATCH 13/43] Add button to go to content overview --- .../ndc-country-full-component.jsx | 12 +++++++--- .../ndc-country-full-styles.scss | 22 ++----------------- .../ndc-country-full/ndc-country-full.js | 3 ++- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/app/javascript/app/pages/ndc-country-full/ndc-country-full-component.jsx b/app/javascript/app/pages/ndc-country-full/ndc-country-full-component.jsx index 259a22268b..9022cd8c5a 100644 --- a/app/javascript/app/pages/ndc-country-full/ndc-country-full-component.jsx +++ b/app/javascript/app/pages/ndc-country-full/ndc-country-full-component.jsx @@ -9,6 +9,7 @@ import NoContent from 'components/no-content'; import isEmpty from 'lodash/isEmpty'; import ScrollToHighlightIndex from 'components/scroll-to-highlight-index'; import Sticky from 'react-stickynode'; +import Button from 'components/button'; import layout from 'styles/layout.scss'; import contentStyles from 'styles/themes/content.scss'; @@ -45,17 +46,21 @@ class NDCCountryFull extends PureComponent { contentOptions, contentOptionSelected, route, - fetchCountryNDCFull + fetchCountryNDCFull, + iso } = this.props; return (
-
+
{country && ( )}
+
@@ -94,7 +99,8 @@ NDCCountryFull.propTypes = { contentOptionSelected: PropTypes.object, loaded: PropTypes.bool, search: PropTypes.object, - fetchCountryNDCFull: PropTypes.func + fetchCountryNDCFull: PropTypes.func, + iso: PropTypes.string }; export default NDCCountryFull; diff --git a/app/javascript/app/pages/ndc-country-full/ndc-country-full-styles.scss b/app/javascript/app/pages/ndc-country-full/ndc-country-full-styles.scss index 6f36e015f3..62d906e1d2 100644 --- a/app/javascript/app/pages/ndc-country-full/ndc-country-full-styles.scss +++ b/app/javascript/app/pages/ndc-country-full/ndc-country-full-styles.scss @@ -3,27 +3,9 @@ .twoFold { @extend %grid; - @include msGridColumns(7fr, 5fr); + @include msGridColumns(3fr, 1fr); - grid-template-columns: 7fr 5fr; -} - -.twoFoldReversed { - @extend %grid; - - @include msGridColumns(5fr, 7fr); - - grid-template-columns: 5fr 7fr; -} - -.backButton { - margin-right: 20px; -} - -.backIcon { - fill: $white; - width: 60px; - height: 60px; + grid-template-columns: 3fr 1fr; } .actionsWrapper { diff --git a/app/javascript/app/pages/ndc-country-full/ndc-country-full.js b/app/javascript/app/pages/ndc-country-full/ndc-country-full.js index 87e490e245..f3d28b17b1 100644 --- a/app/javascript/app/pages/ndc-country-full/ndc-country-full.js +++ b/app/javascript/app/pages/ndc-country-full/ndc-country-full.js @@ -31,7 +31,8 @@ const mapStateToProps = (state, { match }) => { content: getSelectedContent(contentData), contentOptions: getContentOptions(contentData), contentOptionSelected: getContentOptionSelected(contentData), - search + search, + iso }; }; From 4f2fdff9c7b2a3ffafee9817032975d27d319ce1 Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 15:22:06 +0100 Subject: [PATCH 14/43] get ref for selector to change on search --- .../autocomplete-search-component.jsx | 10 +++++++--- .../autocomplete-search/autocomplete-search.js | 10 +++++++++- .../app/components/dropdown/dropdown-component.jsx | 4 +++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx b/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx index c7e7ad3b8f..7c09edab8a 100644 --- a/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx +++ b/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx @@ -11,14 +11,18 @@ import styles from './autocomplete-search-styles.scss'; class CountriesSelect extends PureComponent { render() { - const { handleValueClick, setAutocompleteSearch, searchList } = this.props; + const { handleValueClick, handleSearchChange, searchList } = this.props; return (
{ + this.selectorElement = el; + return this.selectorElement; + }} + onSearchChange={handleSearchChange} onValueChange={handleValueClick} value={null} hideResetButton @@ -38,7 +42,7 @@ class CountriesSelect extends PureComponent { CountriesSelect.propTypes = { handleValueClick: Proptypes.func.isRequired, - setAutocompleteSearch: Proptypes.func.isRequired, + handleSearchChange: Proptypes.func.isRequired, searchList: Proptypes.array }; diff --git a/app/javascript/app/components/autocomplete-search/autocomplete-search.js b/app/javascript/app/components/autocomplete-search/autocomplete-search.js index bf0d5c5708..b076ca0740 100644 --- a/app/javascript/app/components/autocomplete-search/autocomplete-search.js +++ b/app/javascript/app/components/autocomplete-search/autocomplete-search.js @@ -33,10 +33,18 @@ class AutocompleteSearchContainer extends PureComponent { this.props.history.push(option.path); }; + handleSearchChange = search => { + this.props.setAutocompleteSearch(search); + if (this.selectorElement) { + this.selectorElement.highlightFirstSelectableOption(); + } + }; + render() { return createElement(AutocompleteSearchComponent, { ...this.props, - handleValueClick: this.handleValueClick + handleValueClick: this.handleValueClick, + handleSearchChange: this.handleSearchChange }); } } diff --git a/app/javascript/app/components/dropdown/dropdown-component.jsx b/app/javascript/app/components/dropdown/dropdown-component.jsx index 1cf3043531..047ae1a395 100644 --- a/app/javascript/app/components/dropdown/dropdown-component.jsx +++ b/app/javascript/app/components/dropdown/dropdown-component.jsx @@ -28,6 +28,7 @@ const Dropdown = props => { )} > } {...props} @@ -48,7 +49,8 @@ Dropdown.propTypes = { theme: PropTypes.object, hasSearch: PropTypes.bool, disabled: PropTypes.bool, - blueBorder: PropTypes.bool + blueBorder: PropTypes.bool, + selectorRef: PropTypes.func }; export default themr('Dropdown', styles)(Dropdown); From a2395cee5dcd2d60a59b603cb84a6aa104e0a943 Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 15:46:05 +0100 Subject: [PATCH 15/43] add favicons --- .../images/favicons/android-icon-144x144.png | Bin 0 -> 9967 bytes .../images/favicons/android-icon-192x192.png | Bin 0 -> 13307 bytes .../images/favicons/android-icon-36x36.png | Bin 0 -> 1848 bytes .../images/favicons/android-icon-48x48.png | Bin 0 -> 2988 bytes .../images/favicons/android-icon-72x72.png | Bin 0 -> 4278 bytes .../images/favicons/android-icon-96x96.png | Bin 0 -> 5714 bytes .../images/favicons/apple-icon-114x114.png | Bin 0 -> 6978 bytes .../images/favicons/apple-icon-120x120.png | Bin 0 -> 7540 bytes .../images/favicons/apple-icon-144x144.png | Bin 0 -> 9967 bytes .../images/favicons/apple-icon-152x152.png | Bin 0 -> 10765 bytes .../images/favicons/apple-icon-180x180.png | Bin 0 -> 14026 bytes .../images/favicons/apple-icon-57x57.png | Bin 0 -> 3450 bytes .../images/favicons/apple-icon-60x60.png | Bin 0 -> 3606 bytes .../images/favicons/apple-icon-72x72.png | Bin 0 -> 4278 bytes .../images/favicons/apple-icon-76x76.png | Bin 0 -> 4565 bytes .../favicons/apple-icon-precomposed.png | Bin 0 -> 13796 bytes app/assets/images/favicons/apple-icon.png | Bin 0 -> 13796 bytes app/assets/images/favicons/browserconfig.xml | 2 + app/assets/images/favicons/favicon-16x16.png | Bin 0 -> 1073 bytes app/assets/images/favicons/favicon-32x32.png | Bin 0 -> 1713 bytes app/assets/images/favicons/favicon-96x96.png | Bin 0 -> 5714 bytes app/assets/images/favicons/favicon.ico | Bin 0 -> 1150 bytes app/assets/images/favicons/manifest.json | 41 ++++++++++++++++++ .../images/favicons/ms-icon-144x144.png | Bin 0 -> 9967 bytes .../images/favicons/ms-icon-150x150.png | Bin 0 -> 10572 bytes .../images/favicons/ms-icon-310x310.png | Bin 0 -> 32219 bytes app/assets/images/favicons/ms-icon-70x70.png | Bin 0 -> 4159 bytes app/views/layouts/application.html.erb | 21 +++++++++ 28 files changed, 64 insertions(+) create mode 100644 app/assets/images/favicons/android-icon-144x144.png create mode 100644 app/assets/images/favicons/android-icon-192x192.png create mode 100644 app/assets/images/favicons/android-icon-36x36.png create mode 100644 app/assets/images/favicons/android-icon-48x48.png create mode 100644 app/assets/images/favicons/android-icon-72x72.png create mode 100644 app/assets/images/favicons/android-icon-96x96.png create mode 100644 app/assets/images/favicons/apple-icon-114x114.png create mode 100644 app/assets/images/favicons/apple-icon-120x120.png create mode 100644 app/assets/images/favicons/apple-icon-144x144.png create mode 100644 app/assets/images/favicons/apple-icon-152x152.png create mode 100644 app/assets/images/favicons/apple-icon-180x180.png create mode 100644 app/assets/images/favicons/apple-icon-57x57.png create mode 100644 app/assets/images/favicons/apple-icon-60x60.png create mode 100644 app/assets/images/favicons/apple-icon-72x72.png create mode 100644 app/assets/images/favicons/apple-icon-76x76.png create mode 100644 app/assets/images/favicons/apple-icon-precomposed.png create mode 100644 app/assets/images/favicons/apple-icon.png create mode 100644 app/assets/images/favicons/browserconfig.xml create mode 100644 app/assets/images/favicons/favicon-16x16.png create mode 100644 app/assets/images/favicons/favicon-32x32.png create mode 100644 app/assets/images/favicons/favicon-96x96.png create mode 100644 app/assets/images/favicons/favicon.ico create mode 100644 app/assets/images/favicons/manifest.json create mode 100644 app/assets/images/favicons/ms-icon-144x144.png create mode 100644 app/assets/images/favicons/ms-icon-150x150.png create mode 100644 app/assets/images/favicons/ms-icon-310x310.png create mode 100644 app/assets/images/favicons/ms-icon-70x70.png diff --git a/app/assets/images/favicons/android-icon-144x144.png b/app/assets/images/favicons/android-icon-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..7197b4bab6ce688950843c2155d0df043a91e327 GIT binary patch literal 9967 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 MC#5V|DPbJ)U;A-i*8l(j literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/android-icon-192x192.png b/app/assets/images/favicons/android-icon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..ccded1e6aef2e9bbf887fd607f2cebd03ebdfda7 GIT binary patch literal 13307 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/android-icon-36x36.png b/app/assets/images/favicons/android-icon-36x36.png new file mode 100644 index 0000000000000000000000000000000000000000..8b742ddd2e6a532ad1c7627a23750e53af7d9ca5 GIT binary patch literal 1848 zcmZ{i2~?9;7RNtO7*s5XD3Q?;IzTHD@+D*=B5RPOB!PqkkVOb&1wtYufLbdc2!U!v z5kh5Psw@Js2m(T3N`ZX?fA2f*DLm{q7nd%q`8d@I{?ru@}#m{H#aw@X6CvEhVC@9 z=2hG;sA+0>-23+Z0Kpg$oQ3t0uE5V?tq|6W^$ zKF(tg-qYB@2k#RRmYC7j-Cy%#2ii+iTHi7;ImHymqnLub%?~Ej(+u(X;L~YM9Z!fS zB0s^>Kf%!Tah`fux~>iFi!V9f4ZMs6J!W}c+l-7Nw5z*rJk8f%(`Xm!6W!Yyi=(!<`kKd0v`6hn3|nq zNfLHrXrhFS^2XN3eS>XX{keDQTsU$xQ+U1T?m#(*?Ce(%-Jh>Ms}?Fw#T#c zAKbWc^zd_Y9~O%4HN%dv_a9|l$}d}5UK#%F?b8>pdIw*_N&d+>g@1MM>F9YLcR9EF z*~?Fz{nGwk;uDob=AU*Kp8c@6boEx{^xV9iztr($bWX`V3rdh4&O>>-CR(91aq$zx zUl~_T8Qc5#MkfvZYt+z|X+U6<)VCB=H)R!9AN7++Q?hQ9)xG@p8w*OXpDfvs$b2z8 zV&-ydrC#y2YILUzH(*tj?;b6lm4 zyOjHxYe3&G!vQfV3rowcp)uTZm)6$TE1TL6I{B`ytuZ6x>0##Tkybk)C`5yyt#9R(f73lEEkVipM9X;xlg=+k2zEgk)Y^%i|t9e+h;q+KFSA5|FBOhztcgq~sIPkl$L9f#Z+D?IB=;LULjeIzxkDS}g5vs9jcfB2));a|2%K z@A>qk{nxPcL!=!#J9ng~rYYRXUs5_BIv0$N?lUyCGUx4smy{M&m|9wtmXs8h-zqEC z%Rqw|^q58smD&x1bs;eAm}iLc_VU^Ram1wRPz&>N5&emEa8}ukqZd+&vz50?>c6eb zKkxXD9WI_y9kK-mZe{nGL3>)-Wd$!a-Ds~nnW{N+%?k!&_V+na7j-+R{Z{%%j((n< zVCvycJ%-$`emc08=nQ9P2KxsvCp5U%6&lS-z#2?CONv z7zQ)`x&6EEG7M`|Yti6zU9Al`cw7d8vKaZcwz9v_7?PmP?b>sSYcSZ7g%1j(<`8#( zK?r0;JfNDnQflJIp4R1TyBA&2Zg+8c*M3bcqi)Ajgq}iLS9-Qppf(o~^nGL0vk9!@ zbH7vJzWN1K727ge-{Yyenlt#ws8>zqM(sn5I)=)Zbt$UBhE6HIXZ7rYx)aORqD(qS zr+YX-RWoY0_}&ruz`XLgZf{9P!pwDL%*g2Bs)2DE!BcTEVPs7UA!ne%QQ?Tn^g73{ z`O}3BSME1|^`?)l^r{@!4#>B(Rj6B^=+X5G~=$VmVPffFkAycr=bc6dDf4 zS>vq<2wON*5`zG-2oxI5K;hs*nM_JVB4cA?twmcjWDspFi4wfJI0*>?uq{QFP%aK3 z5=oL!xtJFzBN9C!G#Wq^Z~lP%6Z1z+B2mZ_34}5LWsBAWqLLxT=p&u~Fg_x#Ot^*g z-!avJkOLnfG?ADmJ1yk_e{_YEMhYCNz3G3_p|KzeKw&{!913TH!Sk&c(> z3=aUEoilIRtDqzAkB-<(3cwdd@Sy7>#gVcIi2%vx#E3#AklyQ~{v4(n%H0!HK^E7VaNH literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/android-icon-48x48.png b/app/assets/images/favicons/android-icon-48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..9fcc0a7761165ebfc14c7a66072e00635302743e GIT binary patch literal 2988 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 L5^qsw<`MHBa7Qsw literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/android-icon-72x72.png b/app/assets/images/favicons/android-icon-72x72.png new file mode 100644 index 0000000000000000000000000000000000000000..93cd8e3c14269e23527217b0b3193c218f7ce662 GIT binary patch literal 4278 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/apple-icon-114x114.png b/app/assets/images/favicons/apple-icon-114x114.png new file mode 100644 index 0000000000000000000000000000000000000000..d3292e3e73ca7d0476c99bdbaed06bcac34fe6d3 GIT binary patch literal 6978 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 bZ@~+U;04x4^k;evm;t0LuOU|=V;1mV@T^<| literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/apple-icon-120x120.png b/app/assets/images/favicons/apple-icon-120x120.png new file mode 100644 index 0000000000000000000000000000000000000000..18d208412a45213e50d994b7b3c42c0f88731fd4 GIT binary patch literal 7540 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 MC#5V|DPbJ)U;A-i*8l(j literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/apple-icon-152x152.png b/app/assets/images/favicons/apple-icon-152x152.png new file mode 100644 index 0000000000000000000000000000000000000000..77d9a08cd1be5331fc86c459bc7b4fe5c297dc0a GIT binary patch literal 10765 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/apple-icon-57x57.png b/app/assets/images/favicons/apple-icon-57x57.png new file mode 100644 index 0000000000000000000000000000000000000000..002fde242bd7def41f03376d560dd96a308e0bc3 GIT binary patch literal 3450 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/apple-icon-60x60.png b/app/assets/images/favicons/apple-icon-60x60.png new file mode 100644 index 0000000000000000000000000000000000000000..cc50646d94da88bdac132d558b46bc53d515b6c3 GIT binary patch literal 3606 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 DbGVX$ literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/apple-icon-72x72.png b/app/assets/images/favicons/apple-icon-72x72.png new file mode 100644 index 0000000000000000000000000000000000000000..93cd8e3c14269e23527217b0b3193c218f7ce662 GIT binary patch literal 4278 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/apple-icon-precomposed.png b/app/assets/images/favicons/apple-icon-precomposed.png new file mode 100644 index 0000000000000000000000000000000000000000..a3131ad2aa39d30a441fd248a48cd7a451c19870 GIT binary patch literal 13796 zcmdtJSs4j6VBh<1Lx2N*s%_ZA13MTiQAJS@s6GMt z#RL}k8_G>hS`1V(O?U$QL24$WrU(N0P=i20VIa^G@KVq*2;{*60-YLzKztb>5Uz7h zyQ%y`d zD(F7qA*d&obVGfBOT+jIjfY@|hh$7zWkMSiT=%sQ1lN!M|MH7ujCUxLuymlZQ-(_n zQUrPbQn(sU26|W(P3#J!GL=HMU>ayBGU!9{O*)XIRzU?FOfVhHtAq7dD1U?szO#9J zHkDxQ!ZNx@(jYL#m48E(h!)mp!^hep5ml_nFcB08NCDVwHK~TG zBzqkdSp@7{9cx=NV?Bo*ge;&Tf>nxKweWlR@xrhvuPX>S{n;hyRsWP=@ZlF_jw4!* z5;q|e?glY4gC*v<7P7q-aup6chG|h!T25rsFKnIMPK+}x8VY(rlzP>q1un3pYGDNu zjF?CbnJ6dNk0A@1HX0f`$s4$cI|(ue2C4 zMKEG8Q#repJlfQ6dTAEkU2LFUQ?5Sq)s}SeCVtU;5zenI=VDO><`a=Vors zW?+eH|Bhc;jQdxgLnE!5)OvT&k%Gx~m^wBJXpyiaB?}m1U1YrvMIqo*pNedr$3zA(ygyqih!DaXCmho%_Clo zv*j>xn?r)fD9L!qQ1#^hINwaWo^Ps!ELCcSI9sUIR3Stc1%X2@1>b#No|d@5n4h2^ zs{eIGfT7IxQ(9ls3?BZ_Tm(%jG#|tUdhhWrHo5mZHqUN;fr}*V-F2n#+QfhP6$NjS zOj&lC6K^38tqFS(cxt4&v{=JWEehUoZ@WHF-XLxludAXZg*IT-(3CVd`}Fb$EpUhg zjc{N!CVltX$AmN6q&dT+MfW(4UY)+(58LMpmG2CtY+Z&z5UGeJ_Q$yR{gP$0U0@?) zWrE|EyK!0eb?T2>=k6cA2;F{GklZm!%K8==eJZs=LD7^OCr$~If{BU%-G_V8jZ#Ew z8}a7=Cg9@qyge-4P&jtC9x;PbRT~QC{;mBd`@@K`KbU}RKJ6gmLD1m*_ejjU7kJ=1 zW+Ekew_PWw{Y-zmC#gk9&^wsr z!iFrYopGDT6TjuYhn1^#8gS(K`BX6y8ig5 zAM6kPEdj9(ByWV>e_t+lo!dw;#I(IV^ItaV*~oB)+A=fT+d-NZ_tdXR*s#z+Nl}P| zJemXDZgUa5^sQpuF1H*YKAP_Rt7jAHgWYI85W;c29;%s7M*}f&LV9L1`v_F4zT^2y zhO%Hn--9U~rF$yR=E~P~;dhUXr_7NnPYWVba*BVe5|n5GD}U^9_d#1%Qq{OcHxZ8@ z^sNxxZ#R>~Y(~(ipZ+<)22Iz5KB{Q;xyR;N%F}t#XcZO|p|qsr%Q6A`GAqo*8kOQP zRhOaID1;^{Ki!^ zY6U9I)ed(+Bz(^`1p>6Fc^HZz#83;34-ZIztm_~ ziW$g2(7()1@I%aMph5^~@BSV6XEM}vxj)Btzv$5VHnuJa2>Tq+grY9rpDcjb3QqJI zt_Un~QBP2%B>r-5^>9BEfDBsEZ-qLkOy2SIeVKtmLOE~FIT1TsMFypfF!eg^dOKjk zeU*NF`KA5B07{`WwRZpLr2TWjg(;Nvx^XmW%@SBa{r1T(!xAu1DX6&P$PGaH<4FzV zT4vrAo89CN`n&Ot_vU@YgRyUxmTN_$v4h4(Js%(VbQCynCQXNK(_*40OH6IRWYAJT ziveVetDjM)Hp@D4&Vv5?$2PX@u&pH`ylNE{{giUvH!z&P)5O1JZ~vS8#$w^Jm9FxP zx9rbTITv|WhWDtb`|&fv;p0zQl*3YXU$}Qc!G{oIZ+mF8Z7-U8M@A^n`Km5-6pW6f zX8YYA@qIO9WRF0MThx!=8w#9k1^Vxsxtep{aLC(+U7GG9@4EZt%r>SX@ETK<~ z*Ppa7X=y08&HH@XLtGuCP4lb{hG`Wfu7WgUBy2pJ0BMId`ZWKRTIHx$Z=`bdD9uqO z3%C}j@0d^<`OrIe<14DVAX%(ZSeDu{kl$58;Cs6F!PIw|NIkyB2rC~9F&(mx&tx!R z;A&`wvh6X9y{}RuSIU9!=mM`hBhCHzqTr@*-P_)Uvm3fk$sN~Jhv80d9z}4|uwjd2uGV`92OE35(XZ=O6LBFAh-8Tp=n~+9 zGP(_7v|$<|yifVPSI0bGrG{DAgo3Z5WLr0gN7)qlQgl34#IX9cA9|ktlJu$PcZ@L0 zf2)1){qc>IZ@%UXQ5_4GD+U(GPdl!@P3J2^aPQNH(@Y#q`4*KOdw+==DfgZfB|$;C z1jwZH4f*q~xK7f`R2p1Ow)^__v6|{5` zVzaLQA*1=uQLNl7-K41tpYFy-29ZxG9=a*zt!gfo89mo18X+eIg=sS^Jp%H_cj=Fa6YJfsjVj@{g@g=h46 z;!k7JAl+d!SCzP@6n@rBPH8w5JJvdbtoMyKcK1sZKZm;c=d0!KsXLtNZy!qk6lMyu zZ|#?Y^u+^S#|a+xgZF_O{@8XELmoV`KQw!c$EKEYor$&`jke(~$aPrVV_Mi5UET=X z6!0x-t48j)p#;C~a}RB}8Tj%2uJpCc#xY_^Y#Y;Bpqu{v`}>uuz@v!q?%zi+Xyko~-mFV;z;VzQ-c8wzHCG^MfF0(8Sq%rFe$;iZT`BuT7Pt*Ctq zUHhpdp;LX8N(=;jeTM3`Ck~Op=BR#d5o|h+oq}JVfaWrEBc*7#((UrTq?)O~53B?0 z+Q*P%gC;LyJmvRweKgf6UKDT#kfPW*Dt;NJbaV*>bV1%}#g5f1nrW z?bo3gYrz2~{33Wso%*q>6_~BSeGi_8mqzawb`o(lF~X@itL=#{L4EL@`CR(Dv^l($ z;S==)$NO_$PsgW-yg;14{tsFT3Vh8u)IzH2{ed)jemjm&e|c5P5JEtbs)=BNxay&{ zMIyPE%|gqRu!&c39^EPRDBSDWQQ}?TX7fhW-0(W-QPfFhDP$zi0igd!q3$0;^M3yg^!^*={{k42EuBMA^gZg5Z% z;&-xBg!wnn3Y^xZDOA)*R`ddX*w1$ z!&XK2)YsO+yW-3GcK!RTl=%&D(?66Y+p-oT3r!-)*mOQBY{k0s&-c=+6STK#t$3QR zk)_h$|FHV#CtAfk618DjEcL(9RhL5I;85lSeSKbz#zRNf-J9F?XT)VwyfBmtH%=(2% z4tcuqtM$5&N_YWW(&{d#iPMN^#l%o^nS(t1FRKcjTv1SDN59p~qDK3n|EUg$F`EYW z9J8VWjhK!DE6xznIQEUZ89&enJmlIu`15WOpKs`hoPE%#zE=PnkKex^Ow{iCDd#&1 z-HD09Krto2v#O#$Y+w4L3=#YU<}v019jdpefbOEzf*bYawBupCRH$ro-%kNEPy7Rj ze*)=bZ`X-Q978fTUU|>jV}CJ*4CY-a2&YG;==g80fU+e)W%Ax3$vXk)+b?s|fi(_Y z!T-duDWb?x^DPkr_sDs?Gwr)wUT&M3u)3btS{%M#O23FaoL>I%z8d*30s!eInD55H zH{&9(Z?9v_1(Y;yV_`-eG@?NwFu>A4NHL!`%znQ+z9eXI3R^P$IrXTzV)DSKMTO(K z&Fgz=kp2WP9dOx8J@@IpZ=S3-(o3uf2x_2hG2l^MD7LFWhTcq_Pu4P)SG~YK%DO`2g{;S}r70PB&5MymlS>U#UzI3t1$$j? zHT>$YMalc(96}bU{{^YxCvclf&aJmyqBxD8JRGcC?oH9|NW*6>52=3^M(f_9#fc@m zZ{#<`0*m+D^aQP6L4ENks8-v$Uwf@DiM7+eU6jD8tf0glXrU)Eku}P#M(NREcs3n@ zdTJ)=H;GMg8CHwY^L)16j^r_whkR@W_D$p=vGmS3YzIM~Un>x2j}y5TAUOT@YgD6uy1u#yv1r zl*7<>Y^h;Egl23wdPl_uqVk$ffhQO0tlk1LHXNS258qSNoPS0m^vrp4st|x$e0N}tny_b}2QjnqK&rV^VE&6Wx zAxY_f9T|@Sv9NM3&@$^X{DWMVJI*qjg?`rpYD)#aW!NxKj~4W9Ey*`MQ55)oG;XvZL`FICh$K6q!DxmW9kC}&`+6(!$%{LI#)(C=n1w&P4ONuU_Zxh6anqHo9WFOxQ5M!T=}na6;aZp zs3!EdX$PXdi8yW;4o922i``1}7B7T5ujyJrDc^dmHs}VNkBbg=L&0=X) zOy=a@e!(lE3DrosJ9+}wD!4T}+gmq5R8bbbzQ5G`ZXqcewkceOi(qc<2d%02TlDW+ znf=~Hrw7UNdx@2V&c_G1ggvt&ce`-S)CLW=i&u=k=f>WOZTdgcW7d@$hu*JY_r3WUKnkToxG>4d@Uq>lUE+<0QyyMh+&v2!MdTu6 z5D1lUJ=3sB`z>>E7Lj3G5BsS$v9`j4?Dpd0U?UTvvKHbxQ+gL>N7 zF#_O?OU0YqhHx+O@5jlvAn&!7Dcip3I9MPv1whx((g7{H(ozddF5K6^Wq=@ia_aFR zhBB|F7zhTFOG7#6?Rg~C58seKdMwN39V$a&u(_pSAjB_ti(?RG=6MR$0sC6 zbm4qnfri9*Viw|+j`uX1OL%v?Bs+kftu+Oi+M5Mzx zXA1$;TeV~xK;!_e4c8&6gSDANW1lx%4)v8e3V+qKb6~@-7JyhLNiJ*q{pKgxRa2GN zF7AQ)@C|M_CiFarZkB@V?7qsM=Bz>*S%ZT&t)3ib%$S)Ko@Cd_Fs|% z?e4$bX2bu`b)+8?cQx&FbOc}0Oisb7Ml7A%_HKr7GUi(IF2Gi<{%WbSp;TRel`T4i zh7@k)8)V10FLYbY_s(ACyq9^07ig!sv^FTE&qUS@Xz69sSqUzexAT3csn&@9V)qd8bH_8{o)Z?8mS;)CNIg!J znDWpE)p@xg7E#=e^-`)19*2puhvHzI9HNE+^~*h{M`j`4V})d+>S4%~tG+*%mp+Jo zw75Ft@CYs~jM-*Y#H8}zU$^tibyyTnLkC!o&XFEQ`lD_;Xs=UdrDP`=#2bPJhKYhs ze>nmJQ7JySofgkJD`^m)7|NXTYv+B}jKjkxS}t)fe$>RcXLY-=9hRUb>{ATJ5t$D<{Vq)6Jf3i|1*O5+e8%04Y3+RaAJM@%`wc-# zdQ$N34V?l#%7L3)+bsUsrG?VXZNJ{ld;q71$bP#`UE3eZ`bKG=Gg2cpnCQ-el@>%h<5zeeHD~ojn@U02 zL04`;(?QBtqqbaU7qOkMv`!`N&HSL+Gmkbw9>aCQI_b3avpIYTW{ELx4R1;PkHD24 zCkKCyM#>%v0g7+c#qKF13R5z?@)AJv35zSHx(^#$t-4Efw1uox|J=zEq1#Mj`inAs z_I1l1@XlU}NdLA)-6!{Ej$HpWMPc^Fy=9lP#7$0nA z$;sAsX*RHf(y@j|B<8;Q@F~Hk!X})qPb;;F*Y5HAmCw+u*z(^H=P;qMX_pqnrbsZ( z-u;$`8(tl-60%7G$13w(ah=7xafZ&D;!hDVJTIIFTH}HsY0Vq?k2(Bf71-B_wf#8 z=dL(R<@I1#8}wD^<;4`0ZUU#ES6PKv4E!V~f4Y|}@3Q|VO+})*Gj#k)5E=P>L#U~W zT6|bJc_tCTjfXm2>!-yPmx~JC(58&x>xFh|vcGTkqdIXw8C(!@)109eq>@e7b?n}$ zTpeA3$>N()?!p`1x!udG)GqWErzZ5Nq;iWO29z}bp~g_o2)Von(%5Ft@w5EGeZ|J3 zFd85Ct`_&1-4n|BXs`E)!E%f_S`pw1Zvo!ZTXCW3IoClU3<1dW5)N+g41BC8awo?_ zX?yxBRcT)u;}V<8$G)L3WgLODGJ0FOS%l%Nw~$1^b$06L0)`N;W{yls{mzX9Oa-08 zCbx!`&M#HbS&Hm6@;bLI9A1K2)sbknzSI|~pV#V80c@>i3i}=`N;wz-XL)~lxL;#y zKk}F-l(taZQi<4VVfENQTo<4?{>=dIn@wiJYrH0Rp%-bB0P8YRtMNu%$zts$7@qmW zH~ZRt`iI*ezSX&FL_kqAAL8aSQ0p|DQbMIZVP*6yYKF~@ado#b#=GK^|?Ik&m-U7Dd{`~n-N<|7cmdv+5(&R3Lkb}=WTp57#U!4@Qy z3-AG%c52qxdHHp;0IUU|uGZPIGu4Bq?8))zJv-2(UU_###>Dq~kkw2bo?!bBQB~ux zPaB?#T8I-6SQ$fh3?(31&Yl0Ma{rA`CrLZ3Sq5@_M3`6&SS2guL@Sk7+sQHA!w z73<$BJ&B5Opl=Tx{g%k>LDA$WVSex7!Z3I3%XF>A_Kg2YvgLtPO;joIn`KB%c6qOJ z4blNpacmlUAwPdIdZqc;DU!>iTYj^y8H>2KO8vd6*S5K!d|%*H=h2E3vQM%ZSd_)8 z`!G|ua3D3^&Pi*2F%u5v1A|88lqF>ysV7blws6ME(T#jw>u`z@HMi^SpgQ;c|1|-) z<_l$Y(}i=UT;Gcdu>lDU%8s)Y;=E&y0wj#G+Kr=2foFDr{9cc~X}Uy%eX#HX+DE?m z3*4iE4(z_!mDV4PC!Yv%W?M=F6O)@ElkG#70Fw6oF&pw0K8|@k&W1qtu)e9c8jW>6 zMh2`B?>+dQkoA2O{Aba+0@~A}EXxh~Mqju3Ze#YMRY3L!ggn`fk<1UXJd`*9V7{3uKady=HPz&mP$)OPn*%BOIeP>rR&0+KYQ# zf^yxuLM&8jC?T~ZpGt)k6o(H#y-pq?_rEs$V?+#-N0Q4!&OWP{fBHu+9KF9VzWE(f zQmTIZ^7nDImm*?miik&R6G1Etk{K2X#e~IYIQC}bId68x~dUDy1rglY1H|3LKg^h8`!z2iRXa$7RV^LO!u^4RvP zY}sp17bRMfr=E%@=N^BdSErqqHFdvv?|vToXRFV!AFG!&plX%vVx4yLhoi|i5Q}H`wgyJK{WLLHqkGbg zq-SqZVhiU57%sJzr#MXN&gr85?g$*Fa|bKtqQci4rsogaOOCUySvcLs=*G4LU$#;B zRnpQ+vei0NXp!Pj(GueKf6NF5XB~c~(wU@y!tJg{gui$iXD@RS2B3V*U!f}=Lt(Xg zp6D_Dv2)smDc4^Yvwcab`Gok^J#F$gJLUNJLukFPZvK!$Wqhf~h(Xjd02TlgD{MNy zikSoANF~8;PT!}&KoTC!(E2FZz!J+`qh!qex={GLkU521BYjKWll+(7?c?^N5U91p z)exW~`!^peferY{v|_5Mi6j!5noB+yXde^y?jyS6mr?V@+>42*Q()59L-~n)@R_>; zcz((C!hWZu!93F{g*oqMiYgi=8qXimNNK&XK0PmL?qNR<%%kp1FA-+IVdoC~`uxLb z@i%wxWo7w|06zb;G67oPC4g)y&iQy1mC^|SvJjY2(={ucTLw#^Z~Ts{;V*Ze+QSL@ zKu(n0JZ#Z_H{W^Q?v{Jt(P5?}@$Ds5M6nZ)HGs712uO}sKd94wPXk()yhqeh~cd0bdkS2S8hdvx;J#4EGc<`#sD4`#2H-kc=`0U^iwNIHK8 z{U@3KYZexkrG@o&3*OE-7%*)l;o(;6y-NL$WVt0EH1u_(QTQ27+PR_z)<;rTR|%6n zFr-<;25BdI#r<7+3%O-lR!9dR6#-hPh;}b#`w7vp>zx&#pMqr~wM3g$cK4TeRwEq~ zaAySH`8WJ72dqy85?0DaZBoi;kpLc3Hu>s=CT{@k{R|B(c}j#7wVr11=a9&vot}8A zl&$bgjpX;vhxQO#4lzYKRBU}J%J5YUzG#%88DFONQO-?9C1Bp?g{%(6@mE2Fz zIW?}jb%h_)SR7cE5Es43*8^jxooy*`J@z{I>@>W^SOV~}A0g`eO5<1rL{aa)9niiP zj~GJlQzV728~lEceYpLeO&5&sN3|o?*2V%q7Ucrvfd0~?*@jy$Bb7-Ai7{Tc*FUVW}eLbPQcQI3S ztAXXPGsN7NB;QjB*qv0du76owS|+vb93?oH*{8-wMF(l8UB4Rj#GVP=gD(Ppt$TaC zyTmFfh=E~biV##Ov(%1X!D6J*Uv=F+XnFlw&^|!%8x>0u+_BDqH4&7)2v={<#IlHdDSN)Yvdx3`hLAT#10Z41& zW`f!}v~Mi{TQZy$snta9^V{X<=PWwb-n!OE*6-9fG!ceCVG{kZlSg zv-to!$dH7Xrl$K})PlxJg7?>>*E$FT;A;RZFm<|&b$X0|rDh)$9&pHQIHFM#F#!gX zj4A3B21~#-A_n_cNnda|zNi9lwewnL-6Q8Ams~Olaf=MSH&{l zri@LIhok2WOG%Tna=~fEd}=5%-q@Ac-eH;6KNi(iW|oqo*)gZoPeSJcq)bVTFrp$_ zrj^R#kPD?K7uJWF^tX@%|ATDZEq|6y&5eXERwqH=!Q0T)E1{y0!8qj>@Nz6o{k9g~ zR@}^I|E8OUEg`kiMBFB?89hEj)xm{b?f(n$__!@a@Yhh#%k3M=YP%oFQ0Ye4F zEsCR(dT$DhQ=UpzcDPmdm29js4~{?o!MI4Mg*$(`#A$hbh9cnSNu*S*%u&O7GL%HD zd7h`A0Gf_Ir*~IyOW@lXpr~4H#e{$E(?E?2I~zydafPMM`eziXmxWj2{$b{AKFPcp zROdS0ImtPy7kDtdTnOitb56%BW~v_>K=VnA<-~b!^u(vEB6`1BvHfC3<%q-JnpSZ$ z2ubM6ddF28|8~UN?i{SD0W%gIxEC4g>GDwSYHc^g`~4`d_aL~iV3F@8;c`4c!3Mx^$^|284jrGc@ za&#O?2)se^xPF?7L%Z9}!t(sRgKDpY)C+%?LL-#0?%0m%A4)qsT1%bjaGjn&d26?{Cb%~y)wyT=S>YRScmV5pIVO*)N57LZ!jLAmw z&%(-kNlVR(26}@v&d|6@K2>mj?oO$)P}s=jJKook;aw9*+NA^-$L4~BxG(pz4eI@Q zV?Poo2}QJ`{E63YBGYS2qZ0}d@-%dW2m((X=&GSBOLcYHa` zjA)iCHuk0PPIS5anH+Q&Ld+vCW@yRxhpqgXeR0XiEgT1%XjLGn+9`zKz$4p79-ov#UzA~Pg8dfO8MuFW2wLwfIXb}w6@ zW0m5l8+boJ=ORDnswTO#Cyk{{OBZ5F_pu9t*C-z_R^Nr&RALJjI=c2wYG6ap##x!M z|M{ply5s%B0+@w|{e=8s_55cZm>}5butbaGuSf#d)BAUTbBnO$V)bi_kvA%d-?C#? z?cijgN>FWDHc~@97bhaq-wiGKI4To$XB~_XlqDKE1*430eFcE{nV1m+^pQ=M^lWJ7 zNCtAcDDv*7lZ2hwoA(g34L>4kLZ(jH6jS14Q#;p5M5hF2PZ9M!kT?GyGhcU zgqt_8dD#(_YFiOg+I!AxWMl|qgQ}4VU3f9`ub--eh>yRr8(poB8OlG(9*K2JssEy= zw^P4vvI~Qr?`Q6VrxRIx5Qg1o?fMAh*xfsd)Q5z3f0Sosz+dYJjjlyMX^9Dw*4Da0 z33+*e1}U|`r=ZdRvWoL|#8&7`;Q6agex96LVn(_Z>rmAC2p~a|e`ZC+Oi{x3Kx2_C zk|B+TdHyjUT2zs)_+xNZheaX;V6Auz6+h)$K;DHA(OHB}L?8k88cOO2xCI5Ddlx~+ zQ{xO{XHH&DLmgfY{|JeI`;O(SWr|clzb;*^u}mqp5cw)b))ewd>&vg}(F}J!D3INP z*6yX_O0e%%AiL{w_1uLXIPLJI|NB3wMJ1ydCoteQsQ6fBlfyk2K}EoDaAd?(&nA(- z2gcv{c3F{s{A!9sFu^dKn)~$g4_XXw>EXH(T5Y$cu=yckci34tF$4_{xdOoJr)roO z_g1J1SZV+dbxto(Z1r0=&{`teUjcu+Lb3!}wnPx9kLKa~pZD7%?E(n-28@bMX0dkK zu6x@*w>4L0Vh%W>XSEsjav?@jW zCUKRb9=1kWm`ZOt?Edb|hOCXUL=i#VKKxF?W_v>X=RrBfvKaHw(W(z|wXfVP0@34N zwQ{QLL>=U5{>e`rP9Wk0T&9)?>z@5!fjhO-`NAnnOu(XV zT11}M!bB9lYUcvP;SZc6QwyF9zTfmy z=^vjAgy#hSGmy~TXTqb;#FOL5W8et<;%zZ5X_G7=v0@(?-$cPhhXFLrGD@-1Qo3wh z=EY=g9!MB_MWz0@+|8|QjMB8xMN#DtAd8cXJbW3qCVf3t$IgV`1fH zWMyS!VbfsY<>O@I)xaC1{++?i$tJGKp=e@dV`fLn#l+3T!^letoRWi)g_Dt$jZ~GDi`3fP-I*~`m|$>!f^ zU;{=oIk{SGeEki4APDc@Cu&%GIGFPBNjRB#I9NEk^YN(wL)}2ErSBcgz!;qWHRgZL z$;W4HVPj?O4r0mF{M-wS!2Ul+Xxo^(TZ34t6>H^z0iXWY07V-I3->S17NGyRB4%et zE8zw$C14QR{~Dxj<6{A05fl4XBySs4j6VBh<1Lx2N*s%_ZA13MTiQAJS@s6GMt z#RL}k8_G>hS`1V(O?U$QL24$WrU(N0P=i20VIa^G@KVq*2;{*60-YLzKztb>5Uz7h zyQ%y`d zD(F7qA*d&obVGfBOT+jIjfY@|hh$7zWkMSiT=%sQ1lN!M|MH7ujCUxLuymlZQ-(_n zQUrPbQn(sU26|W(P3#J!GL=HMU>ayBGU!9{O*)XIRzU?FOfVhHtAq7dD1U?szO#9J zHkDxQ!ZNx@(jYL#m48E(h!)mp!^hep5ml_nFcB08NCDVwHK~TG zBzqkdSp@7{9cx=NV?Bo*ge;&Tf>nxKweWlR@xrhvuPX>S{n;hyRsWP=@ZlF_jw4!* z5;q|e?glY4gC*v<7P7q-aup6chG|h!T25rsFKnIMPK+}x8VY(rlzP>q1un3pYGDNu zjF?CbnJ6dNk0A@1HX0f`$s4$cI|(ue2C4 zMKEG8Q#repJlfQ6dTAEkU2LFUQ?5Sq)s}SeCVtU;5zenI=VDO><`a=Vors zW?+eH|Bhc;jQdxgLnE!5)OvT&k%Gx~m^wBJXpyiaB?}m1U1YrvMIqo*pNedr$3zA(ygyqih!DaXCmho%_Clo zv*j>xn?r)fD9L!qQ1#^hINwaWo^Ps!ELCcSI9sUIR3Stc1%X2@1>b#No|d@5n4h2^ zs{eIGfT7IxQ(9ls3?BZ_Tm(%jG#|tUdhhWrHo5mZHqUN;fr}*V-F2n#+QfhP6$NjS zOj&lC6K^38tqFS(cxt4&v{=JWEehUoZ@WHF-XLxludAXZg*IT-(3CVd`}Fb$EpUhg zjc{N!CVltX$AmN6q&dT+MfW(4UY)+(58LMpmG2CtY+Z&z5UGeJ_Q$yR{gP$0U0@?) zWrE|EyK!0eb?T2>=k6cA2;F{GklZm!%K8==eJZs=LD7^OCr$~If{BU%-G_V8jZ#Ew z8}a7=Cg9@qyge-4P&jtC9x;PbRT~QC{;mBd`@@K`KbU}RKJ6gmLD1m*_ejjU7kJ=1 zW+Ekew_PWw{Y-zmC#gk9&^wsr z!iFrYopGDT6TjuYhn1^#8gS(K`BX6y8ig5 zAM6kPEdj9(ByWV>e_t+lo!dw;#I(IV^ItaV*~oB)+A=fT+d-NZ_tdXR*s#z+Nl}P| zJemXDZgUa5^sQpuF1H*YKAP_Rt7jAHgWYI85W;c29;%s7M*}f&LV9L1`v_F4zT^2y zhO%Hn--9U~rF$yR=E~P~;dhUXr_7NnPYWVba*BVe5|n5GD}U^9_d#1%Qq{OcHxZ8@ z^sNxxZ#R>~Y(~(ipZ+<)22Iz5KB{Q;xyR;N%F}t#XcZO|p|qsr%Q6A`GAqo*8kOQP zRhOaID1;^{Ki!^ zY6U9I)ed(+Bz(^`1p>6Fc^HZz#83;34-ZIztm_~ ziW$g2(7()1@I%aMph5^~@BSV6XEM}vxj)Btzv$5VHnuJa2>Tq+grY9rpDcjb3QqJI zt_Un~QBP2%B>r-5^>9BEfDBsEZ-qLkOy2SIeVKtmLOE~FIT1TsMFypfF!eg^dOKjk zeU*NF`KA5B07{`WwRZpLr2TWjg(;Nvx^XmW%@SBa{r1T(!xAu1DX6&P$PGaH<4FzV zT4vrAo89CN`n&Ot_vU@YgRyUxmTN_$v4h4(Js%(VbQCynCQXNK(_*40OH6IRWYAJT ziveVetDjM)Hp@D4&Vv5?$2PX@u&pH`ylNE{{giUvH!z&P)5O1JZ~vS8#$w^Jm9FxP zx9rbTITv|WhWDtb`|&fv;p0zQl*3YXU$}Qc!G{oIZ+mF8Z7-U8M@A^n`Km5-6pW6f zX8YYA@qIO9WRF0MThx!=8w#9k1^Vxsxtep{aLC(+U7GG9@4EZt%r>SX@ETK<~ z*Ppa7X=y08&HH@XLtGuCP4lb{hG`Wfu7WgUBy2pJ0BMId`ZWKRTIHx$Z=`bdD9uqO z3%C}j@0d^<`OrIe<14DVAX%(ZSeDu{kl$58;Cs6F!PIw|NIkyB2rC~9F&(mx&tx!R z;A&`wvh6X9y{}RuSIU9!=mM`hBhCHzqTr@*-P_)Uvm3fk$sN~Jhv80d9z}4|uwjd2uGV`92OE35(XZ=O6LBFAh-8Tp=n~+9 zGP(_7v|$<|yifVPSI0bGrG{DAgo3Z5WLr0gN7)qlQgl34#IX9cA9|ktlJu$PcZ@L0 zf2)1){qc>IZ@%UXQ5_4GD+U(GPdl!@P3J2^aPQNH(@Y#q`4*KOdw+==DfgZfB|$;C z1jwZH4f*q~xK7f`R2p1Ow)^__v6|{5` zVzaLQA*1=uQLNl7-K41tpYFy-29ZxG9=a*zt!gfo89mo18X+eIg=sS^Jp%H_cj=Fa6YJfsjVj@{g@g=h46 z;!k7JAl+d!SCzP@6n@rBPH8w5JJvdbtoMyKcK1sZKZm;c=d0!KsXLtNZy!qk6lMyu zZ|#?Y^u+^S#|a+xgZF_O{@8XELmoV`KQw!c$EKEYor$&`jke(~$aPrVV_Mi5UET=X z6!0x-t48j)p#;C~a}RB}8Tj%2uJpCc#xY_^Y#Y;Bpqu{v`}>uuz@v!q?%zi+Xyko~-mFV;z;VzQ-c8wzHCG^MfF0(8Sq%rFe$;iZT`BuT7Pt*Ctq zUHhpdp;LX8N(=;jeTM3`Ck~Op=BR#d5o|h+oq}JVfaWrEBc*7#((UrTq?)O~53B?0 z+Q*P%gC;LyJmvRweKgf6UKDT#kfPW*Dt;NJbaV*>bV1%}#g5f1nrW z?bo3gYrz2~{33Wso%*q>6_~BSeGi_8mqzawb`o(lF~X@itL=#{L4EL@`CR(Dv^l($ z;S==)$NO_$PsgW-yg;14{tsFT3Vh8u)IzH2{ed)jemjm&e|c5P5JEtbs)=BNxay&{ zMIyPE%|gqRu!&c39^EPRDBSDWQQ}?TX7fhW-0(W-QPfFhDP$zi0igd!q3$0;^M3yg^!^*={{k42EuBMA^gZg5Z% z;&-xBg!wnn3Y^xZDOA)*R`ddX*w1$ z!&XK2)YsO+yW-3GcK!RTl=%&D(?66Y+p-oT3r!-)*mOQBY{k0s&-c=+6STK#t$3QR zk)_h$|FHV#CtAfk618DjEcL(9RhL5I;85lSeSKbz#zRNf-J9F?XT)VwyfBmtH%=(2% z4tcuqtM$5&N_YWW(&{d#iPMN^#l%o^nS(t1FRKcjTv1SDN59p~qDK3n|EUg$F`EYW z9J8VWjhK!DE6xznIQEUZ89&enJmlIu`15WOpKs`hoPE%#zE=PnkKex^Ow{iCDd#&1 z-HD09Krto2v#O#$Y+w4L3=#YU<}v019jdpefbOEzf*bYawBupCRH$ro-%kNEPy7Rj ze*)=bZ`X-Q978fTUU|>jV}CJ*4CY-a2&YG;==g80fU+e)W%Ax3$vXk)+b?s|fi(_Y z!T-duDWb?x^DPkr_sDs?Gwr)wUT&M3u)3btS{%M#O23FaoL>I%z8d*30s!eInD55H zH{&9(Z?9v_1(Y;yV_`-eG@?NwFu>A4NHL!`%znQ+z9eXI3R^P$IrXTzV)DSKMTO(K z&Fgz=kp2WP9dOx8J@@IpZ=S3-(o3uf2x_2hG2l^MD7LFWhTcq_Pu4P)SG~YK%DO`2g{;S}r70PB&5MymlS>U#UzI3t1$$j? zHT>$YMalc(96}bU{{^YxCvclf&aJmyqBxD8JRGcC?oH9|NW*6>52=3^M(f_9#fc@m zZ{#<`0*m+D^aQP6L4ENks8-v$Uwf@DiM7+eU6jD8tf0glXrU)Eku}P#M(NREcs3n@ zdTJ)=H;GMg8CHwY^L)16j^r_whkR@W_D$p=vGmS3YzIM~Un>x2j}y5TAUOT@YgD6uy1u#yv1r zl*7<>Y^h;Egl23wdPl_uqVk$ffhQO0tlk1LHXNS258qSNoPS0m^vrp4st|x$e0N}tny_b}2QjnqK&rV^VE&6Wx zAxY_f9T|@Sv9NM3&@$^X{DWMVJI*qjg?`rpYD)#aW!NxKj~4W9Ey*`MQ55)oG;XvZL`FICh$K6q!DxmW9kC}&`+6(!$%{LI#)(C=n1w&P4ONuU_Zxh6anqHo9WFOxQ5M!T=}na6;aZp zs3!EdX$PXdi8yW;4o922i``1}7B7T5ujyJrDc^dmHs}VNkBbg=L&0=X) zOy=a@e!(lE3DrosJ9+}wD!4T}+gmq5R8bbbzQ5G`ZXqcewkceOi(qc<2d%02TlDW+ znf=~Hrw7UNdx@2V&c_G1ggvt&ce`-S)CLW=i&u=k=f>WOZTdgcW7d@$hu*JY_r3WUKnkToxG>4d@Uq>lUE+<0QyyMh+&v2!MdTu6 z5D1lUJ=3sB`z>>E7Lj3G5BsS$v9`j4?Dpd0U?UTvvKHbxQ+gL>N7 zF#_O?OU0YqhHx+O@5jlvAn&!7Dcip3I9MPv1whx((g7{H(ozddF5K6^Wq=@ia_aFR zhBB|F7zhTFOG7#6?Rg~C58seKdMwN39V$a&u(_pSAjB_ti(?RG=6MR$0sC6 zbm4qnfri9*Viw|+j`uX1OL%v?Bs+kftu+Oi+M5Mzx zXA1$;TeV~xK;!_e4c8&6gSDANW1lx%4)v8e3V+qKb6~@-7JyhLNiJ*q{pKgxRa2GN zF7AQ)@C|M_CiFarZkB@V?7qsM=Bz>*S%ZT&t)3ib%$S)Ko@Cd_Fs|% z?e4$bX2bu`b)+8?cQx&FbOc}0Oisb7Ml7A%_HKr7GUi(IF2Gi<{%WbSp;TRel`T4i zh7@k)8)V10FLYbY_s(ACyq9^07ig!sv^FTE&qUS@Xz69sSqUzexAT3csn&@9V)qd8bH_8{o)Z?8mS;)CNIg!J znDWpE)p@xg7E#=e^-`)19*2puhvHzI9HNE+^~*h{M`j`4V})d+>S4%~tG+*%mp+Jo zw75Ft@CYs~jM-*Y#H8}zU$^tibyyTnLkC!o&XFEQ`lD_;Xs=UdrDP`=#2bPJhKYhs ze>nmJQ7JySofgkJD`^m)7|NXTYv+B}jKjkxS}t)fe$>RcXLY-=9hRUb>{ATJ5t$D<{Vq)6Jf3i|1*O5+e8%04Y3+RaAJM@%`wc-# zdQ$N34V?l#%7L3)+bsUsrG?VXZNJ{ld;q71$bP#`UE3eZ`bKG=Gg2cpnCQ-el@>%h<5zeeHD~ojn@U02 zL04`;(?QBtqqbaU7qOkMv`!`N&HSL+Gmkbw9>aCQI_b3avpIYTW{ELx4R1;PkHD24 zCkKCyM#>%v0g7+c#qKF13R5z?@)AJv35zSHx(^#$t-4Efw1uox|J=zEq1#Mj`inAs z_I1l1@XlU}NdLA)-6!{Ej$HpWMPc^Fy=9lP#7$0nA z$;sAsX*RHf(y@j|B<8;Q@F~Hk!X})qPb;;F*Y5HAmCw+u*z(^H=P;qMX_pqnrbsZ( z-u;$`8(tl-60%7G$13w(ah=7xafZ&D;!hDVJTIIFTH}HsY0Vq?k2(Bf71-B_wf#8 z=dL(R<@I1#8}wD^<;4`0ZUU#ES6PKv4E!V~f4Y|}@3Q|VO+})*Gj#k)5E=P>L#U~W zT6|bJc_tCTjfXm2>!-yPmx~JC(58&x>xFh|vcGTkqdIXw8C(!@)109eq>@e7b?n}$ zTpeA3$>N()?!p`1x!udG)GqWErzZ5Nq;iWO29z}bp~g_o2)Von(%5Ft@w5EGeZ|J3 zFd85Ct`_&1-4n|BXs`E)!E%f_S`pw1Zvo!ZTXCW3IoClU3<1dW5)N+g41BC8awo?_ zX?yxBRcT)u;}V<8$G)L3WgLODGJ0FOS%l%Nw~$1^b$06L0)`N;W{yls{mzX9Oa-08 zCbx!`&M#HbS&Hm6@;bLI9A1K2)sbknzSI|~pV#V80c@>i3i}=`N;wz-XL)~lxL;#y zKk}F-l(taZQi<4VVfENQTo<4?{>=dIn@wiJYrH0Rp%-bB0P8YRtMNu%$zts$7@qmW zH~ZRt`iI*ezSX&FL_kqAAL8aSQ0p|DQbMIZVP*6yYKF~@ado#b#=GK^|?Ik&m-U7Dd{`~n-N<|7cmdv+5(&R3Lkb}=WTp57#U!4@Qy z3-AG%c52qxdHHp;0IUU|uGZPIGu4Bq?8))zJv-2(UU_###>Dq~kkw2bo?!bBQB~ux zPaB?#T8I-6SQ$fh3?(31&Yl0Ma{rA`CrLZ3Sq5@_M3`6&SS2guL@Sk7+sQHA!w z73<$BJ&B5Opl=Tx{g%k>LDA$WVSex7!Z3I3%XF>A_Kg2YvgLtPO;joIn`KB%c6qOJ z4blNpacmlUAwPdIdZqc;DU!>iTYj^y8H>2KO8vd6*S5K!d|%*H=h2E3vQM%ZSd_)8 z`!G|ua3D3^&Pi*2F%u5v1A|88lqF>ysV7blws6ME(T#jw>u`z@HMi^SpgQ;c|1|-) z<_l$Y(}i=UT;Gcdu>lDU%8s)Y;=E&y0wj#G+Kr=2foFDr{9cc~X}Uy%eX#HX+DE?m z3*4iE4(z_!mDV4PC!Yv%W?M=F6O)@ElkG#70Fw6oF&pw0K8|@k&W1qtu)e9c8jW>6 zMh2`B?>+dQkoA2O{Aba+0@~A}EXxh~Mqju3Ze#YMRY3L!ggn`fk<1UXJd`*9V7{3uKady=HPz&mP$)OPn*%BOIeP>rR&0+KYQ# zf^yxuLM&8jC?T~ZpGt)k6o(H#y-pq?_rEs$V?+#-N0Q4!&OWP{fBHu+9KF9VzWE(f zQmTIZ^7nDImm*?miik&R6G1Etk{K2X#e~IYIQC}bId68x~dUDy1rglY1H|3LKg^h8`!z2iRXa$7RV^LO!u^4RvP zY}sp17bRMfr=E%@=N^BdSErqqHFdvv?|vToXRFV!AFG!&plX%vVx4yLhoi|i5Q}H`wgyJK{WLLHqkGbg zq-SqZVhiU57%sJzr#MXN&gr85?g$*Fa|bKtqQci4rsogaOOCUySvcLs=*G4LU$#;B zRnpQ+vei0NXp!Pj(GueKf6NF5XB~c~(wU@y!tJg{gui$iXD@RS2B3V*U!f}=Lt(Xg zp6D_Dv2)smDc4^Yvwcab`Gok^J#F$gJLUNJLukFPZvK!$Wqhf~h(Xjd02TlgD{MNy zikSoANF~8;PT!}&KoTC!(E2FZz!J+`qh!qex={GLkU521BYjKWll+(7?c?^N5U91p z)exW~`!^peferY{v|_5Mi6j!5noB+yXde^y?jyS6mr?V@+>42*Q()59L-~n)@R_>; zcz((C!hWZu!93F{g*oqMiYgi=8qXimNNK&XK0PmL?qNR<%%kp1FA-+IVdoC~`uxLb z@i%wxWo7w|06zb;G67oPC4g)y&iQy1mC^|SvJjY2(={ucTLw#^Z~Ts{;V*Ze+QSL@ zKu(n0JZ#Z_H{W^Q?v{Jt(P5?}@$Ds5M6nZ)HGs712uO}sKd94wPXk()yhqeh~cd0bdkS2S8hdvx;J#4EGc<`#sD4`#2H-kc=`0U^iwNIHK8 z{U@3KYZexkrG@o&3*OE-7%*)l;o(;6y-NL$WVt0EH1u_(QTQ27+PR_z)<;rTR|%6n zFr-<;25BdI#r<7+3%O-lR!9dR6#-hPh;}b#`w7vp>zx&#pMqr~wM3g$cK4TeRwEq~ zaAySH`8WJ72dqy85?0DaZBoi;kpLc3Hu>s=CT{@k{R|B(c}j#7wVr11=a9&vot}8A zl&$bgjpX;vhxQO#4lzYKRBU}J%J5YUzG#%88DFONQO-?9C1Bp?g{%(6@mE2Fz zIW?}jb%h_)SR7cE5Es43*8^jxooy*`J@z{I>@>W^SOV~}A0g`eO5<1rL{aa)9niiP zj~GJlQzV728~lEceYpLeO&5&sN3|o?*2V%q7Ucrvfd0~?*@jy$Bb7-Ai7{Tc*FUVW}eLbPQcQI3S ztAXXPGsN7NB;QjB*qv0du76owS|+vb93?oH*{8-wMF(l8UB4Rj#GVP=gD(Ppt$TaC zyTmFfh=E~biV##Ov(%1X!D6J*Uv=F+XnFlw&^|!%8x>0u+_BDqH4&7)2v={<#IlHdDSN)Yvdx3`hLAT#10Z41& zW`f!}v~Mi{TQZy$snta9^V{X<=PWwb-n!OE*6-9fG!ceCVG{kZlSg zv-to!$dH7Xrl$K})PlxJg7?>>*E$FT;A;RZFm<|&b$X0|rDh)$9&pHQIHFM#F#!gX zj4A3B21~#-A_n_cNnda|zNi9lwewnL-6Q8Ams~Olaf=MSH&{l zri@LIhok2WOG%Tna=~fEd}=5%-q@Ac-eH;6KNi(iW|oqo*)gZoPeSJcq)bVTFrp$_ zrj^R#kPD?K7uJWF^tX@%|ATDZEq|6y&5eXERwqH=!Q0T)E1{y0!8qj>@Nz6o{k9g~ zR@}^I|E8OUEg`kiMBFB?89hEj)xm{b?f(n$__!@a@Yhh#%k3M=YP%oFQ0Ye4F zEsCR(dT$DhQ=UpzcDPmdm29js4~{?o!MI4Mg*$(`#A$hbh9cnSNu*S*%u&O7GL%HD zd7h`A0Gf_Ir*~IyOW@lXpr~4H#e{$E(?E?2I~zydafPMM`eziXmxWj2{$b{AKFPcp zROdS0ImtPy7kDtdTnOitb56%BW~v_>K=VnA<-~b!^u(vEB6`1BvHfC3<%q-JnpSZ$ z2ubM6ddF28|8~UN?i{SD0W%gIxEC4g>GDwSYHc^g`~4`d_aL~iV3F@8;c`4c!3Mx^$^|284jrGc@ za&#O?2)se^xPF?7L%Z9}!t(sRgKDpY)C+%?LL-#0?%0m%A4)qsT1%bjaGjn&d26?{Cb%~y)wyT=S>YRScmV5pIVO*)N57LZ!jLAmw z&%(-kNlVR(26}@v&d|6@K2>mj?oO$)P}s=jJKook;aw9*+NA^-$L4~BxG(pz4eI@Q zV?Poo2}QJ`{E63YBGYS2qZ0}d@-%dW2m((X=&GSBOLcYHa` zjA)iCHuk0PPIS5anH+Q&Ld+vCW@yRxhpqgXeR0XiEgT1%XjLGn+9`zKz$4p79-ov#UzA~Pg8dfO8MuFW2wLwfIXb}w6@ zW0m5l8+boJ=ORDnswTO#Cyk{{OBZ5F_pu9t*C-z_R^Nr&RALJjI=c2wYG6ap##x!M z|M{ply5s%B0+@w|{e=8s_55cZm>}5butbaGuSf#d)BAUTbBnO$V)bi_kvA%d-?C#? z?cijgN>FWDHc~@97bhaq-wiGKI4To$XB~_XlqDKE1*430eFcE{nV1m+^pQ=M^lWJ7 zNCtAcDDv*7lZ2hwoA(g34L>4kLZ(jH6jS14Q#;p5M5hF2PZ9M!kT?GyGhcU zgqt_8dD#(_YFiOg+I!AxWMl|qgQ}4VU3f9`ub--eh>yRr8(poB8OlG(9*K2JssEy= zw^P4vvI~Qr?`Q6VrxRIx5Qg1o?fMAh*xfsd)Q5z3f0Sosz+dYJjjlyMX^9Dw*4Da0 z33+*e1}U|`r=ZdRvWoL|#8&7`;Q6agex96LVn(_Z>rmAC2p~a|e`ZC+Oi{x3Kx2_C zk|B+TdHyjUT2zs)_+xNZheaX;V6Auz6+h)$K;DHA(OHB}L?8k88cOO2xCI5Ddlx~+ zQ{xO{XHH&DLmgfY{|JeI`;O(SWr|clzb;*^u}mqp5cw)b))ewd>&vg}(F}J!D3INP z*6yX_O0e%%AiL{w_1uLXIPLJI|NB3wMJ1ydCoteQsQ6fBlfyk2K}EoDaAd?(&nA(- z2gcv{c3F{s{A!9sFu^dKn)~$g4_XXw>EXH(T5Y$cu=yckci34tF$4_{xdOoJr)roO z_g1J1SZV+dbxto(Z1r0=&{`teUjcu+Lb3!}wnPx9kLKa~pZD7%?E(n-28@bMX0dkK zu6x@*w>4L0Vh%W>XSEsjav?@jW zCUKRb9=1kWm`ZOt?Edb|hOCXUL=i#VKKxF?W_v>X=RrBfvKaHw(W(z|wXfVP0@34N zwQ{QLL>=U5{>e`rP9Wk0T&9)?>z@5!fjhO-`NAnnOu(XV zT11}M!bB9lYUcvP;SZc6QwyF9zTfmy z=^vjAgy#hSGmy~TXTqb;#FOL5W8et<;%zZ5X_G7=v0@(?-$cPhhXFLrGD@-1Qo3wh z=EY=g9!MB_MWz0@+|8|QjMB8xMN#DtAd8cXJbW3qCVf3t$IgV`1fH zWMyS!VbfsY<>O@I)xaC1{++?i$tJGKp=e@dV`fLn#l+3T!^letoRWi)g_Dt$jZ~GDi`3fP-I*~`m|$>!f^ zU;{=oIk{SGeEki4APDc@Cu&%GIGFPBNjRB#I9NEk^YN(wL)}2ErSBcgz!;qWHRgZL z$;W4HVPj?O4r0mF{M-wS!2Ul+Xxo^(TZ34t6>H^z0iXWY07V-I3->S17NGyRB4%et zE8zw$C14QR{~Dxj<6{A05fl4XBy +#ffffff \ No newline at end of file diff --git a/app/assets/images/favicons/favicon-16x16.png b/app/assets/images/favicons/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..2021247dba6fa28c607e031cd0eec180e3bebc30 GIT binary patch literal 1073 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5)W;Fv z6XN>+|Nq~A{`~p-_xim@&C`~I7q!Gybk13`ZT+4j7jN7>d-c}*^*cX*{kCw!t|Mo! zn1|f?n(-t2)f7K!)SHv>NFeqKlDQffn<3E4>iY@QRXqfQp_wNmRkDk1E z{l>jV;YH2wKYreN;Kc4@XP>=#Gh^lEkDtFfZThSU3d|&LcNc~ZR#^`qhqJ&VvKUB% z*d7dSC$sHT{GKO_Ki=hkH_y^77vf!y1aGoS(!iws+PD$l%ynh*rAk{z&RIQg^lzyk}Ezm#) z36O~)8Kt>NR#q0YIh242H}9@770C1Gdx3DkC*BJ)?xd(9(#Xp{Nz8 zMjXi;sG9K1l#&dPlFMrpIecG218Q= zOEW_=V>9zr2J#g^CHzR{1ZP&IG8i~HO<8331}GJVBo!Lu$&i+rlM3{{er{e#PJX(6 zT4Gsda(-U1J|bfDje)@2(#$;3G$}33!qCh-ImOg0)y&e+A~hu`)gVa^s6!9vKAv~C Ro&jxO@O1TaS?83{1OS0PrkDT# literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/favicon-32x32.png b/app/assets/images/favicons/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..3a4be98322349e5fb47112c13c83d17a2a5a9b32 GIT binary patch literal 1713 zcmZ{i2~d+q6vx*CwHO7oN+??61uYixC6WXpRfC+;gb)G*JjnGWgaE-L$RS!pND1Oi zAsB3_C~{a(6g;Q`6%bSo%TYv%h$2S_Dj-D8emI?}b*4M>_U-Qbz5jbVyGlCEd5QiP z`T&3>t}YG?#A9Y{(L7{!xR6+dxcNc~l>)%k6N@J~ddPl`jNxn#^6OUh&5}G91{HuP zGXP@Z02oJ(VtN4xg8=Af13*>*U>ur$+rtikxqSL}Ovjm-8O`9E;_{jw)R)4O)Q3)I zoWEMq*79xYDI06BX56fO6K(R^vW&F)a+}cV>%ycT#rV}J&?4_#>0^4Za|LuqZuUm1SP1mE#2YGo}zfwMKf3bKRdeWTAt?ID2qCAj^His z)eM?$XI(F=UP1Qs3XQF(ZT!;SH|zS{;~AGXQF-lM-9_a!o0+0l{R1nucrqo2-;a+w z@xwV$N4W=%PE1aceM1t@<~|}Mxqao zjON`eUrk|^)->do+*{|kyQ;qVY;I9QYkNvop_z++)6>qvyHyy6T^n8e({iuB9vW_J zd*&aZeA%mUh9ibXMq4|&zINlByL=-s_7qDR7jZn@kVH2ix;yhDg5#2T(McntV^dSp zyy#>0Y`G#W%U2$MDlD3&MUrDRQmLJ_tfnIk>n?d{X5uMKDv*`?PBQHG*y62w_O@Fk^QOY#^eWo#Jg;N!3fmv2puHe!J;VYYfq!qY5o5+SO~Dn7cQk zf;cz7+2O!vS~t8>wcgat&v4-~@bh*g=K5I}v~LsjnV?W<6kpU6+gsc7hK5Yg`W2P> zna}kT6W!40oev+FSlC&h(S@7x(CFOC%D}+q`oC!=O>Fv2zcA6AJ!{hC=XcmXIJdt= zn*eDVn)|-f&c)w8fQmZO8iTU*e7n{7%IiV*)Y>sTzynE-F#wnFv$rJVU}AQ9{Q|`?^&^sGPMYzZ zA~TWchOFY(kQZUaa!()QV_ZJFHK$NaWJQnle;Z;Lu6@^Leknh{LAmN1t!y$4qnU0_`fV(tPg8hW~P?Jm!fJMRpIl$TC z4S+NrVT?c0`G@fl@#O)tNaN+Tix9-*BZMjnhUJl=F!<0FHZ(-It7DHW+EvUS`&dF1|R8oilSfu+1n?d-#UPVygv$gF_2-p$z;xZ^SN{Hd-u-0pP49aO{IH;G=v}!=$?wQye`mN{B8Jn z!2e2yk0{XHv6It~1A*$}i7qX1fomM3t`ZDXGxTT+xL~wa*3|%k{8>Sum*F7LIdJRc zPY}q94+Q#U0Ro9fOR-O#pA#hVRLIS<=znWsNlQanQ zpi4zwPTzNSC)dx@)XuLvWJ#phxlt{x%8bUjIH;bx|9y-WqlW$M4A?&EMB|Y)iA52 zDH2f+ZAsp!ppoqXOPh<>0N{s7)P}=ZK<#qp7q_n4pUA$J!PrBO(|tgRmi>X$lRj?efzeAUVWMx2N|Pw zx!WfceAw;BBlv+ID%2l6SB-0gA3oypR24@=YN>`o1W9CIyGiBno9;(yL(|tTdH1($ z@iHH;M4K7jF>lG{1YfQsp+W-AaanCQNMQvWgwHRh7Xr6JKVrGr0O{e&Q?I<>Ew5O- zs9#KsJmTa?WB%_5+zVj?r5n=tzzb?tyUjnMLBDqOFk}`!qGyBata-r)LPO7;yBwRJ zl*$u^j#})WJq~}vy!TZ=p!xG~h)-sN7fPRU!{g>+cZCk}jzHwKm8M!5n@Dn$8d90v z?KV~REZ~jEXudPa4TFqKlOL*1RJx8+ER8WXE*+{tYMU5xx2C4-%rjhK-J(iT3d@zX z#P>Up<=;eAd?)8f%3m^r3PJYS1tL#fm3M99( z7KX^e;pWmN{Sf+qoc<~CsdelUBzz?A&uq6h=5(+;GLA1XH!XH0bHt336EJYq4Sgax z3Al2$O!#Vk@dD5JK5ceRVPUb)^WAr)AM=+F*h?ll>1ZCQYxt2V7F#~S;n@GxDvsGl zmA6Rq$93#)q!CtDl2g(Q)E_?f;MWz^O=_ok$YgK7hj8=`9$qDmRUcWDh?G;+X5wPb z2yd|uszQ_}t7$m5NI6*ZVv%RY5VaEo2&}*&2F>g1G~LN_xt29U-VMR#-q@~nr+B-6 zd|C9ig#x$Pq@$XREjpTaPF5ezBFYx2wRq)1kJ%F(C0m}*2b0k}sxqreAx16zF!s60 zBWRgj{Ia$!3B#X{hFN;E?zOFQpD(>8HFeptvJH0ETIG7?Z0flC427!HcIjmmV{&q84 ztRPt#ABckxsiGPAR7VgmeY@WjGudk{d?K351FWoNW24a!LwMG3E+p=6)aLKF&sE_;~(5>xk&e&=0NK$xJ58;jWp6^j$c0tI~XL}6YI zI5S(l@=6iF+Ry=0rf>ftiPu@0?c(JzEvzUec{5(IDet;3<2B4KjU62i&o&?`Ncrm9 zci6DSK&@m2qTmtosk3Mzqoc;WE11v7EibG#$p=4u`l^D5w`4vECB_}q9cNCg``)6- zy|pwsSxMDLL-wf&$vBe8n7ggGCeirX#-FPAz@Q z{k-g{GgsBCRonrD#hP7dVs>T`!xNk|I?cR`En0Q;;j=1lefWCycM8cPEs~=HY%mXmly25 z9GzGGA*|@_y!$2J()Admf^A?XRi3-S=IHp%-sW@Zi}cGurR%_cjGDP3$C**Zxn$jn zH?qDSg(ye%tPYs~5ow$YM&aG=nOa-x>wKh3zN#a6^cl5fMB}aEt%miAfUuJ3Iw66) zu4`uA!eg$h10Tw1FXDW)6IQEj3q6`Hmr=Ft>o7CRpknc}-3{8yC#A+5|E8m(*7Wi# zTr^LDjZDn+to@mf>)ED#^c|w$y0odRg1N@7h5M==4YK927K}G_c6EE3)}+#*iYqzN zO$5)_L2#XK>SD?cd43D-9#)QNKpK|L7woiIDQx#;z@6C&AjAaYNGI@w|9ECpc%7!q zPEDUbpZciU^|zJ3c{aFBOblWz1j&XyeOtlS&3Ny8+He+~Id8dB*sm3Dtf!y)MZxb; zB+5MG##!q2pL_9VV0;Cg)~jbMK-hoc?N}mEJsh-QhWyeh8{93Z@w4-o*md)$FVxuU zWh^)^%OTc=Cux~_hO;V_+E{qL`mh*EXSR2o=suiXHWk&LVbLeA>M*szodI5+v%t8h3ZpEEiv$>vs_!#4pk&eR zN{I-hei{%2D=2^rvUAZwT`9aRXvJWT%D#@%XB;uYmBKUg-#7JIcMQNDV|tSg(;^Q# zNLW1E?|3*D1FYbiOHTmaGA$lRf|(bCzY$SgAWVG4$%Y z$osa#ag(3y(J*%x)-9DN+PKuKFV?4HEBf$M4>e(TsE(`SkEsy<)xInvd z&^uQ`1syPtkXPNOs-_GI_`0?wFg!{dM*-6|Gl+B;%o(4Nk#CQ$-mjSP%Mv$OFPBq| zGo*ZAIh!H2_VrJWmQ#Z+KG)0^t;ckB)Sor^Xr#cgpGDVB*@p@gqUXfJ(|c5Pq3QmW zC6O>qh0&4XLhSoMfvsKww&;}#Daw|WCo|j&Q`9scLsQ7%@TfTA@=DL60j8b)`}v3M zqxQ^Do^A&BDKbmKRbNECbiE3b{qDB4mB07gu=g>^xP)kBbx$P+fjjgO2il7zmz3%$ z!g3dFaN5-^oNv5*T%G%D%2Nh8rdMaWMX}6YRLBFfu5k( zi8Dh=n&t*&wLi82AOx{%CR0$R4#4+pkH5WbJJ7|ak??PY3N){a9!;`TkC;3eic6y) zrKPLiI`DujCdh>IQgZOwB_t;n8Frl@AYOxX=fl~Sy_kWn?h~Q=L@F8zqaSd86c$O6 zTNQh~f71hTSd7>XO(YP%F#MQrSvb4WUybhP7ZjZ?|BR++#=QNZ^;J>H_i*bMe9CM* zZ`Z`~Pq!fV!j->fTr}STKcrT{;FlaBl7I3$eR`I}q{?#k=*Z%iiTO|EBYMgb8M1zI zL)`J{0E0YzZ@gmcjE+}kjo#NIbF?OpZF ztrlt_q%q;F6Ci5F!y{asm|v-?Aa@_&kS<~o>Y9;9Tlci#sX!{uscEb2*`-*=6)T~a zEZ|I;@#8c!7B*zd(ls_yGJvN(DLwnDpmZ*10IxuJC3fot{2_j!d9pSjiQ17uUlQ{_ zd|ScAD#(1AwwW$Q%`V-Bql=&Q@jNrjY~N`gCuzl+y?O0t#pmjl*6E3eAirXijlWCi zd77qKa72W!$NTu;vZ5T~0S6T)M-t9sZnJFqx!K~Tn%9u;bdjhHW%tENckbpj3qhCK zozfJbHURZ#ytV_2ci@XibRj&NRqieq?i)QZA5>$O5))%#%8TxNl;0B_AGP8ZrLZT~ zN>NRLp`JPB&VA)X)hoU~6~rkq5`s3>A=MFpaP_h>8=8A6(TkVsB+*Yow` zhiy;|y-h`0l^``LB7^M3WcERkb$(5%DG?+P?b4!gV7FC1+b+=(Vjy%w~X z3d)M?O{ns@zG?PYX)ft@Z{1bvt#zaoLtaO>uf_xQ^cE6bqn$>V1v^mH82KbaMPqWT zk4V6aGX(FoqyGS$0zOT~9&C^hkvlgMSXHC$t?vy*gUDY?*QLi!^aM$aMnu@O8 z<)F*3gq6NPa5%QeztQ}&m7XJ&5d{M**Ub}0D29MN6$|;^cp>%Z*po~zJ!?j`i)09a{@*trM|D!1{U>1v-g%< z>-2O45``^CSG~+IGd8MJJ&Hm_n;sM2Lyd0r-qLG0`b!j!VfJo(LaDE%#d=s|6GlJ3 zS@e=HV!wNwgQg27&8c=;!L(oD#mitH;3+~346|Htruqnhsv~)RiuF5vo^N6Qf*c?g zUcEz8H@|T)NAp^q&98VP>n@~H`>2!GoRsJFBmQ4ET6uYylS@{(+Dqw{t z0gLF8UK7Fe%*AdB}M4$kEr8AQg#7NDzS zee~Jri7t9H+r!S&c&daY@Yvno5tg3W>XMUe$AZ}JFo+-bn4%{;-fjL0{J`qJfJ+w? z)`-1-I1v9FGG_BwlIu?K=u3baBn4%5u}JWw6D`k7y9guuS379(Ul>I*EGx$geR)GJFdP|ZWPxiJL!P;dvwBWGzFvz*> z_JoknPm*dm8d3ADk7LG$p??!W;@w?Ki$8eP3sXKeI)6@n8XC+G4Dj+v#g|1%Noa+W z&Kcyx@D1i&^8B{EUYsE4uTI}6t47Wp3RFa8q1MiS-Wjt1xZ;804m_fV^*GopVf5QU z^kdkouxX+`?wJxg^@0FdlNb3pnVWWYyrb)9Gfv2v-uF8Z_~{$jXR~&D5DBTXG{xDS z9}5B-3*C*$pMLK=#_Z3!iXts9{J(%H`wsf7u28A4{n^%jU|o!cC6Q7M7CIZ>!Z1Rm zBceNVykZ1pe9!K&mf87KHwp)R)uLx#r_B#Z2t>}-s7!NzO5M4;i$=)=_3PwIBTK%* zwrPH^TozF{my*m!s{&gVqX5A~obWq$YfVaUs;3%-6?^&!eM%Z#GOb-?40^HjS580` zFRuKRxBUYH(*bwFV5eX2Y99RMfSs&F8`(@UoTyI&@ZUwGR=F`XWkqh|)pZ{`mRAA+ z8eBaW@mrzAp%@6!7ny7CRJkS3rk_flUi*+!kX;7^(CWkIMiO)L24rMB8R8{<4-ej3` z>do2@bvDnt8Q1f$sEW$yA~v8fx=;0TNFRjirK}-#PCx15SN03hMp>gQ4Rzo^SpcuU zXg^)yY+T4z$Z4Bz+{H#$dbIc=v-=J9d#%Z7$Om#E_mPbgovoZxzFPyX70!-!9=0yy zs96(SQ#c$9?_y+$I;hW))a|>wbS7NcAA>(EV33Y=e7^?*&ifKc=2q`|m_<*(Ij z;dgM!s9hclbG!ofa%0I4qGF<%xw!Tbbaf@)pEw_1=JQsv{~_S) zYU5z*_rDALyoFN@2q^y5fONI>^s)3nfK;3+ItB!qtw~*3#R-+SLWg``?b?6#zaWV!|Sp wLRPl6qEKNGYa1b9gs>P?6k%h9;IrZZBzOS(h<9f<01Y4&1x@)Hn8mCA0QB6hJOBUy literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/favicon.ico b/app/assets/images/favicons/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e195eee8f70156ea39a19e6bc65562376661b996 GIT binary patch literal 1150 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 MC#5V|DPbJ)U;A-i*8l(j literal 0 HcmV?d00001 diff --git a/app/assets/images/favicons/ms-icon-150x150.png b/app/assets/images/favicons/ms-icon-150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..ab0549c5e9668c8331863af915df9710be4e3a27 GIT binary patch literal 10572 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%0D-Wf-cq6@l`eVs7{0! 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 literal 0 HcmV?d00001 diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 41da721c94..59f40212a5 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -2,6 +2,27 @@ Climate Watch: Data for Climate Action + <%= favicon_link_tag asset_path('favicons/apple-icon-57x57.png'), rel: 'apple-touch-icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/apple-icon-60x60.png'), rel: 'apple-touch-icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/apple-icon-72x72.png'), rel: 'apple-touch-icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/apple-icon-76x76.png'), rel: 'apple-touch-icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/apple-icon-114x114.png'), rel: 'apple-touch-icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/apple-icon-120x120.png'), rel: 'apple-touch-icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/apple-icon-144x144.png'), rel: 'apple-touch-icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/apple-icon-152x152.png'), rel: 'apple-touch-icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/apple-icon-180x180.png'), rel: 'apple-touch-icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/android-icon-192x192.png'), rel: 'icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/android-icon-144x144.png'), rel: 'icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/android-icon-96x96.png'), rel: 'icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/android-icon-72x72.png'), rel: 'icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/android-icon-48x48.png'), rel: 'icon', type: 'image/png' %> + <%= favicon_link_tag asset_path('favicons/android-icon-36x36.png'), rel: 'icon', type: 'image/png' %> + + <%= favicon_link_tag asset_path('favicons/manifest.json'), rel: 'manifest' %> + + + + <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', From bf9c120d23f359c74a62bd8243971bfbe6a55d77 Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 16:07:10 +0100 Subject: [PATCH 16/43] fix: show Co2 correctly in graph tooltip --- .../charts/tooltip-chart/tooltip-chart-component.jsx | 5 ++++- .../country-ghg-emissions/country-ghg-emissions-selectors.js | 2 +- .../app/components/ghg-emissions/ghg-emissions-selectors.js | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/javascript/app/components/charts/tooltip-chart/tooltip-chart-component.jsx b/app/javascript/app/components/charts/tooltip-chart/tooltip-chart-component.jsx index da29a073e1..4af0eee732 100644 --- a/app/javascript/app/components/charts/tooltip-chart/tooltip-chart-component.jsx +++ b/app/javascript/app/components/charts/tooltip-chart/tooltip-chart-component.jsx @@ -20,7 +20,10 @@ class TooltipChart extends PureComponent { const unit = config.axes && config.axes.yLeft && config.axes.yLeft.unit; return (
- {unit} + {showTotal && (

TOTAL

diff --git a/app/javascript/app/components/country-ghg-emissions/country-ghg-emissions-selectors.js b/app/javascript/app/components/country-ghg-emissions/country-ghg-emissions-selectors.js index 5b8ef826ff..48c3a6b6f6 100644 --- a/app/javascript/app/components/country-ghg-emissions/country-ghg-emissions-selectors.js +++ b/app/javascript/app/components/country-ghg-emissions/country-ghg-emissions-selectors.js @@ -27,7 +27,7 @@ const AXES_CONFIG = { }, yLeft: { name: 'Emissions', - unit: 'CO2e', + unit: 'CO2e', format: 'number' } }; diff --git a/app/javascript/app/components/ghg-emissions/ghg-emissions-selectors.js b/app/javascript/app/components/ghg-emissions/ghg-emissions-selectors.js index 4c0d53b00e..0fa1b2c2f2 100644 --- a/app/javascript/app/components/ghg-emissions/ghg-emissions-selectors.js +++ b/app/javascript/app/components/ghg-emissions/ghg-emissions-selectors.js @@ -64,7 +64,7 @@ const AXES_CONFIG = { }, yLeft: { name: 'Emissions', - unit: 'CO2e', + unit: 'CO2e', format: 'number' } }; From 97fbbe02d65f5dc0899db09c68cf9120b453e083 Mon Sep 17 00:00:00 2001 From: j8seangel Date: Thu, 2 Nov 2017 16:15:00 +0100 Subject: [PATCH 17/43] Include polyfils --- app/views/layouts/application.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 59f40212a5..15cdbde910 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -28,6 +28,7 @@ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <% if @isProduction %> <%= stylesheet_pack_tag 'main' %> <% end %> From fabbf3eff3717f77ef09034989e6a087f7ba6453 Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 18:42:33 +0100 Subject: [PATCH 18/43] fix: always highlight first in list when component updates to fix enter key selection --- .../autocomplete-search-component.jsx | 4 -- .../dropdown/dropdown-component.jsx | 57 +++++++++++-------- .../multiselect/multiselect-component.jsx | 7 +++ 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx b/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx index 7c09edab8a..797b354704 100644 --- a/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx +++ b/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx @@ -18,10 +18,6 @@ class CountriesSelect extends PureComponent { className={theme.dropdownOptionWithArrow} placeholder={'e.g. "Brazil", "energy", "deforestation targets"'} options={searchList} - selectorRef={el => { - this.selectorElement = el; - return this.selectorElement; - }} onSearchChange={handleSearchChange} onValueChange={handleValueClick} value={null} diff --git a/app/javascript/app/components/dropdown/dropdown-component.jsx b/app/javascript/app/components/dropdown/dropdown-component.jsx index 047ae1a395..ec8ebb440a 100644 --- a/app/javascript/app/components/dropdown/dropdown-component.jsx +++ b/app/javascript/app/components/dropdown/dropdown-component.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import { ReactSelectize, SimpleSelect } from 'react-selectize'; // eslint-disable-line import PropTypes from 'prop-types'; import Icon from 'components/icon'; @@ -11,32 +11,41 @@ import dropdownArrowWhite from 'assets/icons/dropdown-arrow-white.svg'; import theme from 'styles/themes/dropdown/react-selectize.scss'; import styles from './dropdown-styles.scss'; -const Dropdown = props => { - const arrow = props.white ? dropdownArrowWhite : dropdownArrow; +class Dropdown extends PureComponent { + componentDidUpdate() { + this.selectorElement.highlightFirstSelectableOption(); + } - return ( -
- {props.label && {props.label}} -
+ {this.props.label && ( + {this.props.label} )} - > - } - {...props} - /> +
+ { + this.selectorElement = el; + }} + className={cx(this.props.className, this.props.disabled)} + renderToggleButton={() => } + {...this.props} + /> +
-
- ); -}; + ); + } +} Dropdown.propTypes = { label: PropTypes.string, diff --git a/app/javascript/app/components/multiselect/multiselect-component.jsx b/app/javascript/app/components/multiselect/multiselect-component.jsx index 34af1904db..f7cd2be646 100644 --- a/app/javascript/app/components/multiselect/multiselect-component.jsx +++ b/app/javascript/app/components/multiselect/multiselect-component.jsx @@ -18,6 +18,10 @@ class Multiselect extends Component { }; } + componentDidUpdate() { + this.selectorElement.highlightFirstSelectableOption(); + } + getSelectorValue() { const { values, options } = this.props; const { search } = this.state; @@ -45,6 +49,9 @@ class Multiselect extends Component {
{this.getSelectorValue()}
{ + this.selectorElement = el; + }} filterOptions={filterOptions} renderValue={() => } renderOption={option => { From fc3fab969c06e31c65c2e673ec4dde67ff82ceda Mon Sep 17 00:00:00 2001 From: Edward Brett Date: Thu, 2 Nov 2017 18:46:04 +0100 Subject: [PATCH 19/43] clean up --- .../autocomplete-search-component.jsx | 6 +++--- .../autocomplete-search/autocomplete-search.js | 10 +--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx b/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx index 797b354704..c7e7ad3b8f 100644 --- a/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx +++ b/app/javascript/app/components/autocomplete-search/autocomplete-search-component.jsx @@ -11,14 +11,14 @@ import styles from './autocomplete-search-styles.scss'; class CountriesSelect extends PureComponent { render() { - const { handleValueClick, handleSearchChange, searchList } = this.props; + const { handleValueClick, setAutocompleteSearch, searchList } = this.props; return (
{ - this.props.setAutocompleteSearch(search); - if (this.selectorElement) { - this.selectorElement.highlightFirstSelectableOption(); - } - }; - render() { return createElement(AutocompleteSearchComponent, { ...this.props, - handleValueClick: this.handleValueClick, - handleSearchChange: this.handleSearchChange + handleValueClick: this.handleValueClick }); } } From 0e3221e0fdc47e55068b348e81f7f9844e11c9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Fri, 27 Oct 2017 18:10:23 +0100 Subject: [PATCH 20/43] Change db schema --- ...27155954_drop_old_indc_tables_and_views.rb | 29 + ...27164649_create_new_indc_category_types.rb | 10 + .../20171027164650_create_new_indc_sources.rb | 10 + ...171027164651_create_new_indc_indicators.rb | 16 + ...171027164658_create_new_indc_categories.rb | 18 + ...2_create_new_indc_indicators_categories.rb | 20 + .../20171027164708_create_new_indc_sectors.rb | 15 + .../20171027164714_create_new_indc_labels.rb | 13 + .../20171027164736_create_new_indc_values.rb | 24 + ...71027170320_create_new_indc_submissions.rb | 15 + db/schema.rb | 549 ------------------ 11 files changed, 170 insertions(+), 549 deletions(-) create mode 100644 db/migrate/20171027155954_drop_old_indc_tables_and_views.rb create mode 100644 db/migrate/20171027164649_create_new_indc_category_types.rb create mode 100644 db/migrate/20171027164650_create_new_indc_sources.rb create mode 100644 db/migrate/20171027164651_create_new_indc_indicators.rb create mode 100644 db/migrate/20171027164658_create_new_indc_categories.rb create mode 100644 db/migrate/20171027164702_create_new_indc_indicators_categories.rb create mode 100644 db/migrate/20171027164708_create_new_indc_sectors.rb create mode 100644 db/migrate/20171027164714_create_new_indc_labels.rb create mode 100644 db/migrate/20171027164736_create_new_indc_values.rb create mode 100644 db/migrate/20171027170320_create_new_indc_submissions.rb diff --git a/db/migrate/20171027155954_drop_old_indc_tables_and_views.rb b/db/migrate/20171027155954_drop_old_indc_tables_and_views.rb new file mode 100644 index 0000000000..bf8e70241d --- /dev/null +++ b/db/migrate/20171027155954_drop_old_indc_tables_and_views.rb @@ -0,0 +1,29 @@ +class DropOldIndcTablesAndViews < ActiveRecord::Migration[5.1] + def change + drop_view :indc_values, materialized: true + drop_view :indc_sectors, materialized: true + drop_view :indc_labels, materialized: true + drop_view :indc_indicators_categories, materialized: true + drop_view :indc_indicators, materialized: true + drop_view :indc_categories, materialized: true + + drop_table :global_indc_categories + drop_table :global_indc_indicators + drop_table :global_indc_indicators_categories + + drop_table :cait_indc_values + drop_table :cait_indc_labels + drop_table :cait_indc_indicators_categories + drop_table :cait_indc_categories + drop_table :cait_indc_indicators + drop_table :cait_indc_submissions + drop_table :cait_indc_charts + + drop_table :wb_indc_values + drop_table :wb_indc_indicators_categories + drop_table :wb_indc_categories + drop_table :wb_indc_indicators + drop_table :wb_indc_indicator_types + drop_table :wb_indc_sectors + end +end diff --git a/db/migrate/20171027164649_create_new_indc_category_types.rb b/db/migrate/20171027164649_create_new_indc_category_types.rb new file mode 100644 index 0000000000..adddbf7a81 --- /dev/null +++ b/db/migrate/20171027164649_create_new_indc_category_types.rb @@ -0,0 +1,10 @@ +class CreateNewIndcCategoryTypes < ActiveRecord::Migration[5.1] + def change + create_table :indc_category_types do |t| + t.text :name, null: false + t.timestamps + end + + add_index :indc_category_types, :name, unique: true + end +end diff --git a/db/migrate/20171027164650_create_new_indc_sources.rb b/db/migrate/20171027164650_create_new_indc_sources.rb new file mode 100644 index 0000000000..08eb1e0191 --- /dev/null +++ b/db/migrate/20171027164650_create_new_indc_sources.rb @@ -0,0 +1,10 @@ +class CreateNewIndcSources < ActiveRecord::Migration[5.1] + def change + create_table :indc_sources do |t| + t.text :name, null: false + t.timestamps + end + + add_index :indc_sources, :name, unique: true + end +end diff --git a/db/migrate/20171027164651_create_new_indc_indicators.rb b/db/migrate/20171027164651_create_new_indc_indicators.rb new file mode 100644 index 0000000000..cb60ad74e5 --- /dev/null +++ b/db/migrate/20171027164651_create_new_indc_indicators.rb @@ -0,0 +1,16 @@ +class CreateNewIndcIndicators < ActiveRecord::Migration[5.1] + def change + create_table :indc_indicators do |t| + t.references :source, foreign_key: { + to_table: :indc_sources, + on_delete: :cascade + }, null: false + t.text :slug, null: false + t.text :name, null: false + t.text :description + t.timestamps + end + + add_index :indc_indicators, :slug, unique: true + end +end diff --git a/db/migrate/20171027164658_create_new_indc_categories.rb b/db/migrate/20171027164658_create_new_indc_categories.rb new file mode 100644 index 0000000000..bd62481253 --- /dev/null +++ b/db/migrate/20171027164658_create_new_indc_categories.rb @@ -0,0 +1,18 @@ +class CreateNewIndcCategories < ActiveRecord::Migration[5.1] + def change + create_table :indc_categories do |t| + t.references :source, foreign_key: { + to_table: :indc_sources, + on_delete: :cascade + }, null: false + t.references :category_type, foreign_key: { + to_table: :indc_category_types, + on_delete: :cascade + }, null: false + t.text :slug, null: false + t.text :name, null: false + t.timestamps + end + add_index :indc_categories, :slug, unique: true + end +end diff --git a/db/migrate/20171027164702_create_new_indc_indicators_categories.rb b/db/migrate/20171027164702_create_new_indc_indicators_categories.rb new file mode 100644 index 0000000000..161a49790e --- /dev/null +++ b/db/migrate/20171027164702_create_new_indc_indicators_categories.rb @@ -0,0 +1,20 @@ +class CreateNewIndcIndicatorsCategories < ActiveRecord::Migration[5.1] + def change + create_table :indc_indicators_categories do |t| + t.references :indicator, foreign_key: { + to_table: :indc_indicators, + on_delete: :cascade + }, null: false + t.references :category, foreign_key: { + to_table: :indc_categories, + on_delete: :cascade + }, null: false + t.timestamps + end + + add_index :indc_indicators_categories, [ + :indicator_id, + :category_id + ], unique: true, name: :indc_indicators_categories_uniq + end +end diff --git a/db/migrate/20171027164708_create_new_indc_sectors.rb b/db/migrate/20171027164708_create_new_indc_sectors.rb new file mode 100644 index 0000000000..1a5898ba16 --- /dev/null +++ b/db/migrate/20171027164708_create_new_indc_sectors.rb @@ -0,0 +1,15 @@ +class CreateNewIndcSectors < ActiveRecord::Migration[5.1] + def change + create_table :indc_sectors do |t| + t.references :parent, foreign_key: { + to_table: :indc_sectors, + on_delete: :cascade + } + t.text :name, null: false + t.text :slug, null: false + t.timestamps + end + + add_index :indc_sectors, :slug, unique: true + end +end diff --git a/db/migrate/20171027164714_create_new_indc_labels.rb b/db/migrate/20171027164714_create_new_indc_labels.rb new file mode 100644 index 0000000000..d546f039ff --- /dev/null +++ b/db/migrate/20171027164714_create_new_indc_labels.rb @@ -0,0 +1,13 @@ +class CreateNewIndcLabels < ActiveRecord::Migration[5.1] + def change + create_table :indc_labels do |t| + t.references :indicator, foreign_key: { + to_table: :indc_indicators, + on_delete: :cascade + }, null: false + t.text :value, null: false + t.integer :index, null: false + t.timestamps + end + end +end diff --git a/db/migrate/20171027164736_create_new_indc_values.rb b/db/migrate/20171027164736_create_new_indc_values.rb new file mode 100644 index 0000000000..19416cd9bb --- /dev/null +++ b/db/migrate/20171027164736_create_new_indc_values.rb @@ -0,0 +1,24 @@ +class CreateNewIndcValues < ActiveRecord::Migration[5.1] + def change + create_table :indc_values do |t| + t.references :indicator, foreign_key: { + to_table: :indc_indicators, + on_delete: :cascade + }, null: false + t.references :location, foreign_key: { + to_table: :locations, + on_delete: :cascade + }, null: false + t.references :sector, foreign_key: { + to_table: :indc_sectors, + on_delete: :cascade + } + t.references :label, foreign_key: { + to_table: :indc_labels, + on_delete: :cascade + } + t.text :value, null: false + t.timestamps + end + end +end diff --git a/db/migrate/20171027170320_create_new_indc_submissions.rb b/db/migrate/20171027170320_create_new_indc_submissions.rb new file mode 100644 index 0000000000..a56e0b86dd --- /dev/null +++ b/db/migrate/20171027170320_create_new_indc_submissions.rb @@ -0,0 +1,15 @@ +class CreateNewIndcSubmissions < ActiveRecord::Migration[5.1] + def change + create_table :indc_submissions do |t| + t.references :location, foreign_key: { + to_table: :locations, + on_delete: :cascade + }, null: false + t.text :submission_type, null: false + t.text :language, null: false + t.date :submission_date, null: false + t.text :url, null: false + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4b6c0c2156..e69de29bb2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,549 +0,0 @@ -# This file is auto-generated from the current state of the database. Instead -# of editing this file, please use the migrations feature of Active Record to -# incrementally modify your database, and then regenerate this schema definition. -# -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). -# -# It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema.define(version: 20171031100456) do - - # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" - - create_table "adaptation_values", force: :cascade do |t| - t.bigint "variable_id" - t.bigint "location_id" - t.text "string_value" - t.float "number_value" - t.boolean "boolean_value" - t.integer "absolute_rank" - t.float "relative_rank" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["location_id"], name: "index_adaptation_values_on_location_id" - t.index ["variable_id"], name: "index_adaptation_values_on_variable_id" - end - - create_table "adaptation_variables", force: :cascade do |t| - t.text "slug" - t.text "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["slug"], name: "index_adaptation_variables_on_slug", unique: true - end - - create_table "cait_indc_categories", force: :cascade do |t| - t.text "name", null: false - t.text "slug", null: false - t.text "category_type" - t.index ["category_type"], name: "index_cait_indc_categories_on_category_type" - end - - create_table "cait_indc_charts", force: :cascade do |t| - t.text "name", null: false - end - - create_table "cait_indc_indicators", force: :cascade do |t| - t.bigint "chart_id" - t.text "name", null: false - t.text "slug", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["chart_id"], name: "index_cait_indc_indicators_on_chart_id" - end - - create_table "cait_indc_indicators_categories", force: :cascade do |t| - t.bigint "indicator_id" - t.bigint "category_id" - t.index ["category_id"], name: "index_cait_indc_indicators_categories_on_category_id" - t.index ["indicator_id", "category_id"], name: "indicator_id_category_id_index", unique: true - t.index ["indicator_id"], name: "index_cait_indc_indicators_categories_on_indicator_id" - end - - create_table "cait_indc_labels", force: :cascade do |t| - t.bigint "indicator_id" - t.text "name", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "index" - t.index ["indicator_id"], name: "index_cait_indc_labels_on_indicator_id" - end - - create_table "cait_indc_submissions", force: :cascade do |t| - t.bigint "location_id" - t.text "submission_type", null: false - t.text "language", null: false - t.date "submission_date", null: false - t.text "url", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["location_id"], name: "index_cait_indc_submissions_on_location_id" - end - - create_table "cait_indc_values", force: :cascade do |t| - t.bigint "location_id" - t.bigint "indicator_id" - t.bigint "label_id" - t.text "value", null: false - t.index ["indicator_id"], name: "index_cait_indc_values_on_indicator_id" - t.index ["label_id"], name: "index_cait_indc_values_on_label_id" - t.index ["location_id"], name: "index_cait_indc_values_on_location_id" - end - - create_table "global_indc_categories", force: :cascade do |t| - t.text "name" - t.text "slug" - t.bigint "parent_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["parent_id"], name: "index_global_indc_categories_on_parent_id" - end - - create_table "global_indc_indicators", force: :cascade do |t| - t.bigint "cait_indicator_id" - t.bigint "wb_indicator_id" - t.index ["cait_indicator_id"], name: "index_global_indc_indicators_on_cait_indicator_id" - t.index ["wb_indicator_id"], name: "index_global_indc_indicators_on_wb_indicator_id" - end - - create_table "global_indc_indicators_categories", force: :cascade do |t| - t.bigint "indicator_id" - t.bigint "category_id" - t.index ["category_id"], name: "index_global_indc_indicators_categories_on_category_id" - t.index ["indicator_id"], name: "index_global_indc_indicators_categories_on_indicator_id" - end - - create_table "historical_emissions_data_sources", force: :cascade do |t| - t.text "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "historical_emissions_gases", force: :cascade do |t| - t.text "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "historical_emissions_gwps", force: :cascade do |t| - t.text "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "historical_emissions_records", force: :cascade do |t| - t.bigint "location_id" - t.bigint "data_source_id" - t.bigint "sector_id" - t.bigint "gas_id" - t.jsonb "emissions" - t.bigint "gwp_id" - t.index ["data_source_id"], name: "index_historical_emissions_records_on_data_source_id" - t.index ["gas_id"], name: "index_historical_emissions_records_on_gas_id" - t.index ["gwp_id"], name: "index_historical_emissions_records_on_gwp_id" - t.index ["location_id"], name: "index_historical_emissions_records_on_location_id" - t.index ["sector_id"], name: "index_historical_emissions_records_on_sector_id" - end - - create_table "historical_emissions_sectors", force: :cascade do |t| - t.bigint "parent_id" - t.bigint "data_source_id" - t.text "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "annex_type" - t.index ["data_source_id"], name: "index_historical_emissions_sectors_on_data_source_id" - t.index ["parent_id"], name: "index_historical_emissions_sectors_on_parent_id" - end - - create_table "location_members", force: :cascade do |t| - t.bigint "location_id" - t.bigint "member_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["location_id"], name: "index_location_members_on_location_id" - t.index ["member_id"], name: "index_location_members_on_member_id" - end - - create_table "locations", force: :cascade do |t| - t.text "iso_code3", null: false - t.text "pik_name" - t.text "cait_name" - t.text "ndcp_navigators_name" - t.text "wri_standard_name", null: false - t.text "unfccc_group" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "iso_code2", null: false - t.text "location_type", null: false - t.boolean "show_in_cw", default: true - t.json "topojson" - t.jsonb "centroid" - t.index ["iso_code2"], name: "index_locations_on_iso_code2" - t.index ["iso_code3"], name: "index_locations_on_iso_code3" - end - - create_table "ndc_sdg_goals", force: :cascade do |t| - t.text "number", null: false - t.text "title", null: false - t.text "cw_title", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "colour", null: false - t.index ["number"], name: "index_ndc_sdg_goals_on_number", unique: true - end - - create_table "ndc_sdg_ndc_target_sectors", force: :cascade do |t| - t.bigint "ndc_target_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.bigint "sector_id" - t.index ["ndc_target_id"], name: "index_ndc_sdg_ndc_target_sectors_on_ndc_target_id" - t.index ["sector_id"], name: "index_ndc_sdg_ndc_target_sectors_on_sector_id" - end - - create_table "ndc_sdg_ndc_targets", force: :cascade do |t| - t.bigint "ndc_id" - t.bigint "target_id" - t.text "indc_text" - t.text "status" - t.text "climate_response" - t.text "type_of_information" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["ndc_id"], name: "index_ndc_sdg_ndc_targets_on_ndc_id" - t.index ["target_id"], name: "index_ndc_sdg_ndc_targets_on_target_id" - end - - create_table "ndc_sdg_sectors", force: :cascade do |t| - t.text "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "ndc_sdg_targets", force: :cascade do |t| - t.text "number", null: false - t.text "title", null: false - t.bigint "goal_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["goal_id"], name: "index_ndc_sdg_targets_on_goal_id" - t.index ["number"], name: "index_ndc_sdg_targets_on_number", unique: true - end - - create_table "ndcs", force: :cascade do |t| - t.bigint "location_id" - t.text "full_text" - t.tsvector "full_text_tsv" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "document_type", default: "ndc" - t.text "language" - t.index ["full_text_tsv"], name: "index_ndcs_on_full_text_tsv", using: :gin - t.index ["location_id"], name: "index_ndcs_on_location_id" - end - - create_table "quantification_labels", force: :cascade do |t| - t.text "name", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["name"], name: "index_quantification_labels_on_name", unique: true - end - - create_table "quantification_values", force: :cascade do |t| - t.bigint "location_id" - t.bigint "label_id" - t.integer "year", limit: 2 - t.float "first_value" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.float "second_value" - t.index ["label_id"], name: "index_quantification_values_on_label_id" - t.index ["location_id"], name: "index_quantification_values_on_location_id" - end - - create_table "socioeconomic_indicators", force: :cascade do |t| - t.bigint "location_id" - t.integer "year", limit: 2, null: false - t.bigint "gdp" - t.integer "gdp_rank", limit: 2 - t.float "gdp_per_capita" - t.integer "gdp_per_capita_rank" - t.bigint "population" - t.integer "population_rank", limit: 2 - t.float "population_growth" - t.integer "population_growth_rank", limit: 2 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["location_id", "year"], name: "index_socioeconomic_indicators_on_location_id_and_year", unique: true - t.index ["location_id"], name: "index_socioeconomic_indicators_on_location_id" - end - - create_table "timeline_documents", force: :cascade do |t| - t.bigint "source_id" - t.bigint "location_id" - t.text "link" - t.text "text" - t.date "date" - t.text "language" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["location_id"], name: "index_timeline_documents_on_location_id" - t.index ["source_id"], name: "index_timeline_documents_on_source_id" - end - - create_table "timeline_notes", force: :cascade do |t| - t.bigint "document_id" - t.text "note" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["document_id"], name: "index_timeline_notes_on_document_id" - end - - create_table "timeline_sources", force: :cascade do |t| - t.text "name" - end - - create_table "wb_extra_country_data", force: :cascade do |t| - t.bigint "location_id" - t.integer "year" - t.bigint "gdp" - t.bigint "population" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["location_id"], name: "index_wb_extra_country_data_on_location_id" - end - - create_table "wb_indc_categories", force: :cascade do |t| - t.text "name", null: false - t.text "slug", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "wb_indc_indicator_types", force: :cascade do |t| - t.text "name", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "wb_indc_indicators", force: :cascade do |t| - t.bigint "indicator_type_id" - t.text "code", null: false - t.text "name", null: false - t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["indicator_type_id"], name: "index_wb_indc_indicators_on_indicator_type_id" - end - - create_table "wb_indc_indicators_categories", force: :cascade do |t| - t.bigint "indicator_id" - t.bigint "category_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["category_id"], name: "index_wb_indc_indicators_categories_on_category_id" - t.index ["indicator_id"], name: "index_wb_indc_indicators_categories_on_indicator_id" - end - - create_table "wb_indc_sectors", force: :cascade do |t| - t.bigint "parent_id" - t.text "name", null: false - t.index ["parent_id"], name: "index_wb_indc_sectors_on_parent_id" - end - - create_table "wb_indc_values", force: :cascade do |t| - t.bigint "indicator_id" - t.bigint "location_id" - t.bigint "sector_id" - t.text "value" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["indicator_id"], name: "index_wb_indc_values_on_indicator_id" - t.index ["location_id"], name: "index_wb_indc_values_on_location_id" - t.index ["sector_id"], name: "index_wb_indc_values_on_sector_id" - end - - create_table "wri_metadata_acronyms", force: :cascade do |t| - t.text "acronym" - t.text "definition" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["acronym"], name: "index_wri_metadata_acronyms_on_acronym", unique: true - end - - create_table "wri_metadata_properties", force: :cascade do |t| - t.text "slug" - t.text "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["slug"], name: "index_wri_metadata_properties_on_slug", unique: true - end - - create_table "wri_metadata_sources", force: :cascade do |t| - t.text "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "wri_metadata_values", force: :cascade do |t| - t.bigint "source_id" - t.bigint "property_id" - t.text "value" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["property_id"], name: "index_wri_metadata_values_on_property_id" - t.index ["source_id", "property_id"], name: "source_id_property_id_index", unique: true - t.index ["source_id"], name: "index_wri_metadata_values_on_source_id" - end - - add_foreign_key "adaptation_values", "adaptation_variables", column: "variable_id", on_delete: :cascade - add_foreign_key "adaptation_values", "locations", on_delete: :cascade - add_foreign_key "cait_indc_indicators", "cait_indc_charts", column: "chart_id", on_delete: :cascade - add_foreign_key "cait_indc_indicators_categories", "cait_indc_categories", column: "category_id", on_delete: :cascade - add_foreign_key "cait_indc_indicators_categories", "cait_indc_indicators", column: "indicator_id", on_delete: :cascade - add_foreign_key "cait_indc_labels", "cait_indc_indicators", column: "indicator_id", on_delete: :cascade - add_foreign_key "cait_indc_submissions", "locations", on_delete: :cascade - add_foreign_key "cait_indc_values", "cait_indc_indicators", column: "indicator_id", on_delete: :cascade - add_foreign_key "cait_indc_values", "cait_indc_labels", column: "label_id", on_delete: :cascade - add_foreign_key "cait_indc_values", "locations", on_delete: :cascade - add_foreign_key "global_indc_categories", "global_indc_categories", column: "parent_id", on_delete: :cascade - add_foreign_key "global_indc_indicators", "cait_indc_indicators", column: "cait_indicator_id", on_delete: :cascade - add_foreign_key "global_indc_indicators", "wb_indc_indicators", column: "wb_indicator_id", on_delete: :cascade - add_foreign_key "global_indc_indicators_categories", "global_indc_categories", column: "category_id", on_delete: :cascade - add_foreign_key "global_indc_indicators_categories", "global_indc_indicators", column: "indicator_id", on_delete: :cascade - add_foreign_key "historical_emissions_records", "historical_emissions_data_sources", column: "data_source_id", on_delete: :cascade - add_foreign_key "historical_emissions_records", "historical_emissions_gases", column: "gas_id", on_delete: :cascade - add_foreign_key "historical_emissions_records", "historical_emissions_gwps", column: "gwp_id" - add_foreign_key "historical_emissions_records", "historical_emissions_sectors", column: "sector_id", on_delete: :cascade - add_foreign_key "historical_emissions_records", "locations", on_delete: :cascade - add_foreign_key "historical_emissions_sectors", "historical_emissions_data_sources", column: "data_source_id", on_delete: :cascade - add_foreign_key "historical_emissions_sectors", "historical_emissions_sectors", column: "parent_id", on_delete: :cascade - add_foreign_key "location_members", "locations", column: "member_id", on_delete: :cascade - add_foreign_key "location_members", "locations", on_delete: :cascade - add_foreign_key "ndc_sdg_ndc_target_sectors", "ndc_sdg_ndc_targets", column: "ndc_target_id", on_delete: :cascade - add_foreign_key "ndc_sdg_ndc_target_sectors", "ndc_sdg_sectors", column: "sector_id", on_delete: :cascade - add_foreign_key "ndc_sdg_ndc_targets", "ndc_sdg_targets", column: "target_id", on_delete: :cascade - add_foreign_key "ndc_sdg_ndc_targets", "ndcs", on_delete: :cascade - add_foreign_key "ndc_sdg_targets", "ndc_sdg_goals", column: "goal_id" - add_foreign_key "ndcs", "locations", on_delete: :cascade - add_foreign_key "quantification_values", "locations", on_delete: :cascade - add_foreign_key "quantification_values", "quantification_labels", column: "label_id", on_delete: :cascade - add_foreign_key "socioeconomic_indicators", "locations", on_delete: :cascade - add_foreign_key "timeline_documents", "locations", on_delete: :cascade - add_foreign_key "timeline_documents", "timeline_sources", column: "source_id", on_delete: :cascade - add_foreign_key "timeline_notes", "timeline_documents", column: "document_id", on_delete: :cascade - add_foreign_key "wb_extra_country_data", "locations", on_delete: :cascade - add_foreign_key "wb_indc_indicators", "wb_indc_indicator_types", column: "indicator_type_id", on_delete: :cascade - add_foreign_key "wb_indc_indicators_categories", "wb_indc_categories", column: "category_id", on_delete: :cascade - add_foreign_key "wb_indc_indicators_categories", "wb_indc_indicators", column: "indicator_id", on_delete: :cascade - add_foreign_key "wb_indc_sectors", "wb_indc_sectors", column: "parent_id", on_delete: :cascade - add_foreign_key "wb_indc_values", "locations", on_delete: :cascade - add_foreign_key "wb_indc_values", "wb_indc_indicators", column: "indicator_id", on_delete: :cascade - add_foreign_key "wb_indc_values", "wb_indc_sectors", column: "sector_id", on_delete: :cascade - add_foreign_key "wri_metadata_values", "wri_metadata_properties", column: "property_id", on_delete: :cascade - add_foreign_key "wri_metadata_values", "wri_metadata_sources", column: "source_id", on_delete: :cascade - - create_view "indc_indicators", materialized: true, sql_definition: <<-SQL - SELECT ('cait'::text || cait_indc_indicators.id) AS id, - 'cait'::text AS source, - cait_indc_indicators.name, - cait_indc_indicators.slug, - NULL::text AS description - FROM cait_indc_indicators - UNION ALL - SELECT ('wb'::text || wb_indc_indicators.id) AS id, - 'wb'::text AS source, - wb_indc_indicators.name, - wb_indc_indicators.code AS slug, - wb_indc_indicators.description - FROM wb_indc_indicators; - SQL - - add_index "indc_indicators", ["id"], name: "index_indc_indicators_on_id" - - create_view "indc_categories", materialized: true, sql_definition: <<-SQL - SELECT ('cait'::text || cait_indc_categories.id) AS id, - 'cait'::text AS source, - cait_indc_categories.name, - cait_indc_categories.slug, - cait_indc_categories.category_type - FROM cait_indc_categories - UNION ALL - SELECT ('wb'::text || wb_indc_categories.id) AS id, - 'wb'::text AS source, - wb_indc_categories.name, - wb_indc_categories.slug, - 'overview'::text AS category_type - FROM wb_indc_categories; - SQL - - add_index "indc_categories", ["id"], name: "index_indc_categories_on_id" - - create_view "indc_values", materialized: true, sql_definition: <<-SQL - SELECT ('cait'::text || cait_indc_values.id) AS id, - 'cait'::text AS source, - cait_indc_values.location_id, - ('cait'::text || cait_indc_values.indicator_id) AS indicator_id, - ('cait'::text || cait_indc_values.label_id) AS label_id, - NULL::text AS sector_id, - cait_indc_values.value - FROM cait_indc_values - UNION ALL - SELECT ('wb'::text || wb_indc_values.id) AS id, - 'wb'::text AS source, - wb_indc_values.location_id, - ('wb'::text || wb_indc_values.indicator_id) AS indicator_id, - NULL::text AS label_id, - ('wb'::text || wb_indc_values.sector_id) AS sector_id, - wb_indc_values.value - FROM wb_indc_values; - SQL - - add_index "indc_values", ["indicator_id"], name: "index_indc_values_on_indicator_id" - add_index "indc_values", ["label_id"], name: "index_indc_values_on_label_id" - - create_view "indc_indicators_categories", materialized: true, sql_definition: <<-SQL - SELECT ('cait'::text || cait_indc_indicators_categories.id) AS id, - ('cait'::text || cait_indc_indicators_categories.indicator_id) AS indicator_id, - ('cait'::text || cait_indc_indicators_categories.category_id) AS category_id, - 'cait'::text AS source - FROM cait_indc_indicators_categories - UNION ALL - SELECT ('wb'::text || wb_indc_indicators_categories.id) AS id, - ('wb'::text || wb_indc_indicators_categories.indicator_id) AS indicator_id, - ('wb'::text || wb_indc_indicators_categories.category_id) AS category_id, - 'wb'::text AS source - FROM wb_indc_indicators_categories; - SQL - - add_index "indc_indicators_categories", ["category_id"], name: "index_indc_indicators_categories_on_category_id" - add_index "indc_indicators_categories", ["indicator_id"], name: "index_indc_indicators_categories_on_indicator_id" - - create_view "indc_labels", materialized: true, sql_definition: <<-SQL - SELECT ('cait'::text || cait_indc_labels.id) AS id, - 'cait'::text AS source, - ('cait'::text || cait_indc_labels.indicator_id) AS indicator_id, - cait_indc_labels.name, - cait_indc_labels.index - FROM cait_indc_labels; - SQL - - add_index "indc_labels", ["id"], name: "index_indc_labels_on_id" - add_index "indc_labels", ["indicator_id"], name: "index_indc_labels_on_indicator_id" - - create_view "indc_sectors", materialized: true, sql_definition: <<-SQL - SELECT ('wb'::text || child.id) AS id, - child.name, - ('wb'::text || parent.id) AS parent_id, - parent.name AS parent_name - FROM (wb_indc_sectors child - LEFT JOIN wb_indc_sectors parent ON ((child.parent_id = parent.id))); - SQL - -end From 76002e71c78f9c501cd062a2105f7c017cc34b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Fri, 27 Oct 2017 18:34:02 +0100 Subject: [PATCH 21/43] Add new INDC models --- app/models/indc/category.rb | 10 +++++----- app/models/indc/category_type.rb | 8 ++++++++ app/models/indc/indicator.rb | 9 ++++----- app/models/indc/label.rb | 7 ++----- app/models/indc/sector.rb | 7 +++---- app/models/indc/source.rb | 6 ++++++ app/models/indc/submission.rb | 17 +++++++++++++++++ app/models/indc/value.rb | 6 +----- 8 files changed, 46 insertions(+), 24 deletions(-) create mode 100644 app/models/indc/category_type.rb create mode 100644 app/models/indc/source.rb create mode 100644 app/models/indc/submission.rb diff --git a/app/models/indc/category.rb b/app/models/indc/category.rb index 20a9a6da24..a1d0ac9620 100644 --- a/app/models/indc/category.rb +++ b/app/models/indc/category.rb @@ -1,12 +1,12 @@ module Indc class Category < ApplicationRecord - self.primary_key = :id - + belongs_to :source, class_name: 'Indc::Source' + belongs_to :category_type, class_name: 'Indc::CategoryType' has_and_belongs_to_many :indicators, join_table: :indc_indicators_categories - def readonly? - true - end + validates :slug, presence: true + validates :name, presence: true + validates :slug, uniqueness: true end end diff --git a/app/models/indc/category_type.rb b/app/models/indc/category_type.rb new file mode 100644 index 0000000000..b44fde4c71 --- /dev/null +++ b/app/models/indc/category_type.rb @@ -0,0 +1,8 @@ +module Indc + class CategoryType < ApplicationRecord + has_many :categories, class_name: 'Indc::Category' + + validates :name, uniqueness: true + validates :name, presence: true + end +end diff --git a/app/models/indc/indicator.rb b/app/models/indc/indicator.rb index 69a2595a8d..eec0d6732f 100644 --- a/app/models/indc/indicator.rb +++ b/app/models/indc/indicator.rb @@ -1,14 +1,13 @@ module Indc class Indicator < ApplicationRecord - self.primary_key = :id - + belongs_to :source, class_name: 'Indc::Source' has_many :values, class_name: 'Indc::Value' has_many :labels, class_name: 'Indc::Label' has_and_belongs_to_many :categories, join_table: :indc_indicators_categories - def readonly? - true - end + validates :slug, presence: true + validates :name, presence: true + validates :slug, uniqueness: true end end diff --git a/app/models/indc/label.rb b/app/models/indc/label.rb index 6e67c814b9..fe728f6d12 100644 --- a/app/models/indc/label.rb +++ b/app/models/indc/label.rb @@ -1,11 +1,8 @@ module Indc class Label < ApplicationRecord - self.primary_key = :id - belongs_to :indicator, class_name: 'Indc::Indicator' - def readonly? - true - end + validates :name, presence: true + validates :index, presence: true end end diff --git a/app/models/indc/sector.rb b/app/models/indc/sector.rb index 372000ba9f..a9d230f939 100644 --- a/app/models/indc/sector.rb +++ b/app/models/indc/sector.rb @@ -1,11 +1,10 @@ module Indc class Sector < ApplicationRecord - self.primary_key = :id belongs_to :parent, class_name: 'Indc::Sector', optional: true has_many :values, class_name: 'Indc::Value' - def readonly? - true - end + validates :slug, presence: true + validates :name, presence: true + validates :slug, uniqueness: true end end diff --git a/app/models/indc/source.rb b/app/models/indc/source.rb new file mode 100644 index 0000000000..70e70871fd --- /dev/null +++ b/app/models/indc/source.rb @@ -0,0 +1,6 @@ +module Indc + class Source < ApplicationRecord + validate :name, presence: true + validate :name, uniqueness: true + end +end diff --git a/app/models/indc/submission.rb b/app/models/indc/submission.rb new file mode 100644 index 0000000000..5d71d19fc7 --- /dev/null +++ b/app/models/indc/submission.rb @@ -0,0 +1,17 @@ +module Indc + class Submission < ApplicationRecord + belongs_to :location + + validates :submission_type, presence: true + validates :language, presence: true + validates :submission_date, presence: true + validates :url, + presence: true, + format: URI.regexp(%w(http https)) + + def submission_date=(val) + write_attribute :submission_date, Date.strptime(val, '%m/%d/%Y') + end + end +end + diff --git a/app/models/indc/value.rb b/app/models/indc/value.rb index 9d0edd03b4..69db920088 100644 --- a/app/models/indc/value.rb +++ b/app/models/indc/value.rb @@ -1,14 +1,10 @@ module Indc class Value < ApplicationRecord - self.primary_key = :id - belongs_to :location belongs_to :indicator, class_name: 'Indc::Indicator' belongs_to :label, class_name: 'Indc::Label', optional: true belongs_to :sector, class_name: 'Indc::Sector', optional: true - def readonly? - true - end + validates :value, presence: true end end From 25f4230dd56d14faaa82d1d14b359a8596fe3c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Fri, 27 Oct 2017 19:22:19 +0100 Subject: [PATCH 22/43] Remove old and create new model factories and spec --- spec/factories/cait_indc_categories.rb | 7 ----- spec/factories/cait_indc_charts.rb | 5 ---- spec/factories/cait_indc_labels.rb | 6 ----- spec/factories/cait_indc_values.rb | 8 ------ spec/factories/indc_categories.rb | 7 +++++ spec/factories/indc_category_types.rb | 6 +++++ ..._indc_indicators.rb => indc_indicators.rb} | 16 +++++------ spec/factories/indc_labels.rb | 7 +++++ spec/factories/indc_sectors.rb | 6 +++++ spec/factories/indc_sources.rb | 5 ++++ spec/factories/indc_submissions.rb | 8 ++++++ spec/factories/indc_values.rb | 9 +++++++ spec/factories/wb_indc_categories.rb | 5 ---- spec/factories/wb_indc_indicator_types.rb | 5 ---- spec/factories/wb_indc_indicators.rb | 21 --------------- spec/factories/wb_indc_sectors.rb | 5 ---- spec/factories/wb_indc_values.rb | 7 ----- spec/models/cait_indc/chart_spec.rb | 9 ------- spec/models/cait_indc/indicator_spec.rb | 9 ------- spec/models/cait_indc/label_spec.rb | 15 ----------- spec/models/cait_indc/value_spec.rb | 15 ----------- spec/models/indc/category_spec.rb | 15 +++++++++++ .../category_type_spec.rb} | 4 +-- spec/models/indc/indicator_spec.rb | 27 +++++++++++++++++++ spec/models/indc/label_spec.rb | 21 +++++++++++++++ spec/models/indc/sector_spec.rb | 15 +++++++++++ .../sector_spec.rb => indc/source_spec.rb} | 4 +-- spec/models/indc/submission_spec.rb | 27 +++++++++++++++++++ spec/models/{wb_indc => indc}/value_spec.rb | 14 +++++++--- spec/models/wb_indc/category_spec.rb | 9 ------- spec/models/wb_indc/indicator_spec.rb | 21 --------------- spec/models/wb_indc/indicator_type_spec.rb | 9 ------- 32 files changed, 174 insertions(+), 173 deletions(-) delete mode 100644 spec/factories/cait_indc_categories.rb delete mode 100644 spec/factories/cait_indc_charts.rb delete mode 100644 spec/factories/cait_indc_labels.rb delete mode 100644 spec/factories/cait_indc_values.rb create mode 100644 spec/factories/indc_categories.rb create mode 100644 spec/factories/indc_category_types.rb rename spec/factories/{cait_indc_indicators.rb => indc_indicators.rb} (61%) create mode 100644 spec/factories/indc_labels.rb create mode 100644 spec/factories/indc_sectors.rb create mode 100644 spec/factories/indc_sources.rb create mode 100644 spec/factories/indc_submissions.rb create mode 100644 spec/factories/indc_values.rb delete mode 100644 spec/factories/wb_indc_categories.rb delete mode 100644 spec/factories/wb_indc_indicator_types.rb delete mode 100644 spec/factories/wb_indc_indicators.rb delete mode 100644 spec/factories/wb_indc_sectors.rb delete mode 100644 spec/factories/wb_indc_values.rb delete mode 100644 spec/models/cait_indc/chart_spec.rb delete mode 100644 spec/models/cait_indc/indicator_spec.rb delete mode 100644 spec/models/cait_indc/label_spec.rb delete mode 100644 spec/models/cait_indc/value_spec.rb create mode 100644 spec/models/indc/category_spec.rb rename spec/models/{cait_indc/category_spec.rb => indc/category_type_spec.rb} (54%) create mode 100644 spec/models/indc/indicator_spec.rb create mode 100644 spec/models/indc/label_spec.rb create mode 100644 spec/models/indc/sector_spec.rb rename spec/models/{wb_indc/sector_spec.rb => indc/source_spec.rb} (58%) create mode 100644 spec/models/indc/submission_spec.rb rename spec/models/{wb_indc => indc}/value_spec.rb (51%) delete mode 100644 spec/models/wb_indc/category_spec.rb delete mode 100644 spec/models/wb_indc/indicator_spec.rb delete mode 100644 spec/models/wb_indc/indicator_type_spec.rb diff --git a/spec/factories/cait_indc_categories.rb b/spec/factories/cait_indc_categories.rb deleted file mode 100644 index 3a283222da..0000000000 --- a/spec/factories/cait_indc_categories.rb +++ /dev/null @@ -1,7 +0,0 @@ -FactoryGirl.define do - factory :cait_indc_category, class: 'CaitIndc::Category' do - name 'MyText' - slug 'my-text' - category_type 'overview' - end -end diff --git a/spec/factories/cait_indc_charts.rb b/spec/factories/cait_indc_charts.rb deleted file mode 100644 index 595640a13b..0000000000 --- a/spec/factories/cait_indc_charts.rb +++ /dev/null @@ -1,5 +0,0 @@ -FactoryGirl.define do - factory :cait_indc_chart, class: 'CaitIndc::Chart' do - name 'MyText' - end -end diff --git a/spec/factories/cait_indc_labels.rb b/spec/factories/cait_indc_labels.rb deleted file mode 100644 index 3c227a6f3e..0000000000 --- a/spec/factories/cait_indc_labels.rb +++ /dev/null @@ -1,6 +0,0 @@ -FactoryGirl.define do - factory :cait_indc_label, class: 'CaitIndc::Label' do - association :indicator, factory: :cait_indc_indicator - name 'MyText' - end -end diff --git a/spec/factories/cait_indc_values.rb b/spec/factories/cait_indc_values.rb deleted file mode 100644 index 5c8ca05215..0000000000 --- a/spec/factories/cait_indc_values.rb +++ /dev/null @@ -1,8 +0,0 @@ -FactoryGirl.define do - factory :cait_indc_value, class: 'CaitIndc::Value' do - location - association :indicator, factory: :cait_indc_indicator - association :label, factory: :cait_indc_label - value 'MyText' - end -end diff --git a/spec/factories/indc_categories.rb b/spec/factories/indc_categories.rb new file mode 100644 index 0000000000..949d633b7c --- /dev/null +++ b/spec/factories/indc_categories.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :indc_category, class: 'Indc::Category' do + name 'MyName' + sequence :slug { |n| "my-slug-" + ('AA'..'ZZ').to_a[n] } + association :category_type, factory: :indc_category_type + end +end diff --git a/spec/factories/indc_category_types.rb b/spec/factories/indc_category_types.rb new file mode 100644 index 0000000000..bc544db547 --- /dev/null +++ b/spec/factories/indc_category_types.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :indc_category_type, class: 'Indc::CategoryType' do + sequence :name { |n| "My Name " + ('AA'..'ZZ').to_a[n] } + end +end + diff --git a/spec/factories/cait_indc_indicators.rb b/spec/factories/indc_indicators.rb similarity index 61% rename from spec/factories/cait_indc_indicators.rb rename to spec/factories/indc_indicators.rb index 46480a32dc..0014b1344f 100644 --- a/spec/factories/cait_indc_indicators.rb +++ b/spec/factories/indc_indicators.rb @@ -1,11 +1,9 @@ FactoryGirl.define do - factory :cait_indc_indicator, class: 'CaitIndc::Indicator' do - association :chart, factory: :cait_indc_chart - name 'MyText' - slug 'my-text' + factory :indc_indicator, class: 'Indc::Indicator' do + name 'MyName' + sequence :slug { |n| "my-slug-" + ('AA'..'ZZ').to_a[n] } - factory :cait_indc_indicator_with_dependants, - class: 'CaitIndc::Indicator' do + trait :with_dependants do transient do values_count 3 labels_count 2 @@ -14,18 +12,18 @@ after(:create) do |indicator, evaluator| labels = create_list( - :cait_indc_label, + :indc_label, evaluator.labels_count, indicator: indicator ) indicator.categories = create_list( - :cait_indc_category, + :indc_category, evaluator.categories_count ) create_list( - :cait_indc_value, + :indc_value, evaluator.values_count, indicator: indicator, label: labels.sample diff --git a/spec/factories/indc_labels.rb b/spec/factories/indc_labels.rb new file mode 100644 index 0000000000..f28ad5b8be --- /dev/null +++ b/spec/factories/indc_labels.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :indc_label, class: 'Indc::Label' do + association :indicator, factory: :indc_indicator + name 'MyText' + sequence :index { |n| (0..99).to_a[n] } + end +end diff --git a/spec/factories/indc_sectors.rb b/spec/factories/indc_sectors.rb new file mode 100644 index 0000000000..48dffbc478 --- /dev/null +++ b/spec/factories/indc_sectors.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :indc_sector, class: 'Indc::Sector' do + name 'MyName' + sequence :slug { |n| "my-slug-" + ('AA'..'ZZ').to_a[n] } + end +end diff --git a/spec/factories/indc_sources.rb b/spec/factories/indc_sources.rb new file mode 100644 index 0000000000..bfcaeffe41 --- /dev/null +++ b/spec/factories/indc_sources.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :indc_source, class: 'Indc::Source' do + sequence :name { |n| "My Name " + ('AA'..'ZZ').to_a[n] } + end +end diff --git a/spec/factories/indc_submissions.rb b/spec/factories/indc_submissions.rb new file mode 100644 index 0000000000..0449b40ef2 --- /dev/null +++ b/spec/factories/indc_submissions.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :indc_submission, class: 'Indc::Submission' do + location + submission_type 'MySubmissionType' + language 'my-lang' + url 'http://internet.tld/path/file.ext' + end +end diff --git a/spec/factories/indc_values.rb b/spec/factories/indc_values.rb new file mode 100644 index 0000000000..190e2e2254 --- /dev/null +++ b/spec/factories/indc_values.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :indc_value, class: 'Indc::Value' do + location + association :indicator, factory: :indc_indicator + association :label, factory: :indc_label + association :sector, factory: :indc_sector + value 'myvalue' + end +end diff --git a/spec/factories/wb_indc_categories.rb b/spec/factories/wb_indc_categories.rb deleted file mode 100644 index c8af415ed6..0000000000 --- a/spec/factories/wb_indc_categories.rb +++ /dev/null @@ -1,5 +0,0 @@ -FactoryGirl.define do - factory :wb_indc_category, class: 'WbIndc::Category' do - name 'MyText' - end -end diff --git a/spec/factories/wb_indc_indicator_types.rb b/spec/factories/wb_indc_indicator_types.rb deleted file mode 100644 index b06683df3a..0000000000 --- a/spec/factories/wb_indc_indicator_types.rb +++ /dev/null @@ -1,5 +0,0 @@ -FactoryGirl.define do - factory :wb_indc_indicator_type, class: 'WbIndc::IndicatorType' do - name 'MyText' - end -end diff --git a/spec/factories/wb_indc_indicators.rb b/spec/factories/wb_indc_indicators.rb deleted file mode 100644 index 3da9c90f7b..0000000000 --- a/spec/factories/wb_indc_indicators.rb +++ /dev/null @@ -1,21 +0,0 @@ -FactoryGirl.define do - factory :wb_indc_indicator, class: 'WbIndc::Indicator' do - association :indicator_type, factory: :wb_indc_indicator_type - name 'MyText' - code 'mytext' - - factory :wb_indc_indicator_with_dependants, - class: 'WbIndc::Indicator' do - transient do - categories_count 2 - end - - after(:create) do |indicator, evaluator| - indicator.categories = create_list( - :wb_indc_category, - evaluator.categories_count - ) - end - end - end -end diff --git a/spec/factories/wb_indc_sectors.rb b/spec/factories/wb_indc_sectors.rb deleted file mode 100644 index bd96c58866..0000000000 --- a/spec/factories/wb_indc_sectors.rb +++ /dev/null @@ -1,5 +0,0 @@ -FactoryGirl.define do - factory :wb_indc_sector, class: 'WbIndc::Sector' do - name 'MyText' - end -end diff --git a/spec/factories/wb_indc_values.rb b/spec/factories/wb_indc_values.rb deleted file mode 100644 index 812d3db1a9..0000000000 --- a/spec/factories/wb_indc_values.rb +++ /dev/null @@ -1,7 +0,0 @@ -FactoryGirl.define do - factory :wb_indc_value, class: 'WbIndc::Value' do - association :indicator, factory: :wb_indc_indicator - location - value 'MyText' - end -end diff --git a/spec/models/cait_indc/chart_spec.rb b/spec/models/cait_indc/chart_spec.rb deleted file mode 100644 index 07f3764690..0000000000 --- a/spec/models/cait_indc/chart_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'rails_helper' - -RSpec.describe CaitIndc::Chart, type: :model do - it 'should be invalid when name not present' do - expect( - FactoryGirl.build(:cait_indc_chart, name: nil) - ).to have(1).errors_on(:name) - end -end diff --git a/spec/models/cait_indc/indicator_spec.rb b/spec/models/cait_indc/indicator_spec.rb deleted file mode 100644 index de83c512b4..0000000000 --- a/spec/models/cait_indc/indicator_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'rails_helper' - -RSpec.describe CaitIndc::Indicator, type: :model do - it 'should be invalid when name not present' do - expect( - FactoryGirl.build(:cait_indc_indicator, name: nil) - ).to have(1).errors_on(:name) - end -end diff --git a/spec/models/cait_indc/label_spec.rb b/spec/models/cait_indc/label_spec.rb deleted file mode 100644 index 61e726593f..0000000000 --- a/spec/models/cait_indc/label_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'rails_helper' - -RSpec.describe CaitIndc::Label, type: :model do - it 'should be invalid when indicator not present' do - expect( - FactoryGirl.build(:cait_indc_label, indicator: nil) - ).to have(1).errors_on(:indicator) - end - - it 'should be invalid when name not present' do - expect( - FactoryGirl.build(:cait_indc_label, name: nil) - ).to have(1).errors_on(:name) - end -end diff --git a/spec/models/cait_indc/value_spec.rb b/spec/models/cait_indc/value_spec.rb deleted file mode 100644 index 3b0fbf6bfc..0000000000 --- a/spec/models/cait_indc/value_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'rails_helper' - -RSpec.describe CaitIndc::Value, type: :model do - it 'should be invalid when location not present' do - expect( - FactoryGirl.build(:cait_indc_value, location: nil) - ).to have(1).errors_on(:location) - end - - it 'should be invalid when indicator not present' do - expect( - FactoryGirl.build(:cait_indc_value, indicator: nil) - ).to have(1).errors_on(:indicator) - end -end diff --git a/spec/models/indc/category_spec.rb b/spec/models/indc/category_spec.rb new file mode 100644 index 0000000000..ae89db92c2 --- /dev/null +++ b/spec/models/indc/category_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +RSpec.describe Indc::Category, type: :model do + it 'should be invalid when name not present' do + expect( + FactoryGirl.build(:indc_category, name: nil) + ).to have(1).errors_on(:name) + end + + it 'should be invalid when slug not present' do + expect( + FactoryGirl.build(:indc_category, slug: nil) + ).to have(1).errors_on(:slug) + end +end diff --git a/spec/models/cait_indc/category_spec.rb b/spec/models/indc/category_type_spec.rb similarity index 54% rename from spec/models/cait_indc/category_spec.rb rename to spec/models/indc/category_type_spec.rb index 461973f65f..749bf62fba 100644 --- a/spec/models/cait_indc/category_spec.rb +++ b/spec/models/indc/category_type_spec.rb @@ -1,9 +1,9 @@ require 'rails_helper' -RSpec.describe CaitIndc::Category, type: :model do +RSpec.describe Indc::CategoryType, type: :model do it 'should be invalid when name not present' do expect( - FactoryGirl.build(:cait_indc_category, name: nil) + FactoryGirl.build(:indc_category_type, name: nil) ).to have(1).errors_on(:name) end end diff --git a/spec/models/indc/indicator_spec.rb b/spec/models/indc/indicator_spec.rb new file mode 100644 index 0000000000..a58422d536 --- /dev/null +++ b/spec/models/indc/indicator_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +describe Indc::Indicator, type: :model do + it 'should be invalid when name not present' do + expect( + FactoryGirl.build(:indc_indicator, name: nil) + ).to have(1).errors_on(:name) + end + + it 'should be invalid when slug not present' do + expect( + FactoryGirl.build(:indc_indicator, sljg: nil) + ).to have(1).errors_on(:slug) + end + + it 'should be invalid when source not present' do + expect( + FactoryGirl.build(:indc_indicator, source: nil) + ).to have(1).errors_on(:source) + end + + it 'should be able to create indicator with all dependant records' do + expect( + FactoryGirl.build(:indc_indicator, :with_dependants) + ).to be_valid + end +end diff --git a/spec/models/indc/label_spec.rb b/spec/models/indc/label_spec.rb new file mode 100644 index 0000000000..84df0ae192 --- /dev/null +++ b/spec/models/indc/label_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +RSpec.describe Indc::Label, type: :model do + it 'should be invalid when indicator not present' do + expect( + FactoryGirl.build(:indc_label, indicator: nil) + ).to have(1).errors_on(:indicator) + end + + it 'should be invalid when name not present' do + expect( + FactoryGirl.build(:indc_label, name: nil) + ).to have(1).errors_on(:name) + end + + it 'should be invalid when index not present' do + expect( + FactoryGirl.build(:indc_label, index: nil) + ).to have(1).errors_on(:index) + end +end diff --git a/spec/models/indc/sector_spec.rb b/spec/models/indc/sector_spec.rb new file mode 100644 index 0000000000..c878c7c9ae --- /dev/null +++ b/spec/models/indc/sector_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +describe Indc::Sector, type: :model do + it 'should be invalid when name not present' do + expect( + FactoryGirl.build(:indc_sector, name: nil) + ).to have(1).errors_on(:name) + end + + it 'should be invalid when slug not present' do + expect( + FactoryGirl.build(:indc_sector, slug: nil) + ).to have(1).errors_on(:slug) + end +end diff --git a/spec/models/wb_indc/sector_spec.rb b/spec/models/indc/source_spec.rb similarity index 58% rename from spec/models/wb_indc/sector_spec.rb rename to spec/models/indc/source_spec.rb index 6bc31e5535..dfa7bc47ab 100644 --- a/spec/models/wb_indc/sector_spec.rb +++ b/spec/models/indc/source_spec.rb @@ -1,9 +1,9 @@ require 'rails_helper' -describe WbIndc::Sector, type: :model do +describe Indc::Source, type: :model do it 'should be invalid when name not present' do expect( - FactoryGirl.build(:wb_indc_sector, name: nil) + FactoryGirl.build(:indc_source, name: nil) ).to have(1).errors_on(:name) end end diff --git a/spec/models/indc/submission_spec.rb b/spec/models/indc/submission_spec.rb new file mode 100644 index 0000000000..538a23ee33 --- /dev/null +++ b/spec/models/indc/submission_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +describe Indc::Submission, type: :model do + it 'should be invalid when location not present' do + expect( + FactoryGirl.build(:indc_submission, location: nil) + ).to have(1).errors_on(:location) + end + + it 'should be invalid when submission_type not present' do + expect( + FactoryGirl.build(:indc_submission, submission_type: nil) + ).to have(1).errors_on(:submission_type) + end + + it 'should be invalid when language not present' do + expect( + FactoryGirl.build(:indc_submission, language: nil) + ).to have(1).errors_on(:language) + end + + it 'should be invalid when url not present' do + expect( + FactoryGirl.build(:indc_submission, url: nil) + ).to have(1).errors_on(:url) + end +end diff --git a/spec/models/wb_indc/value_spec.rb b/spec/models/indc/value_spec.rb similarity index 51% rename from spec/models/wb_indc/value_spec.rb rename to spec/models/indc/value_spec.rb index bcc85fadd4..fb0ca7f9eb 100644 --- a/spec/models/wb_indc/value_spec.rb +++ b/spec/models/indc/value_spec.rb @@ -1,21 +1,27 @@ require 'rails_helper' -describe WbIndc::Value, type: :model do +describe Indc::Value, type: :model do it 'should be invalid when indicator not present' do expect( - FactoryGirl.build(:wb_indc_value, indicator: nil) + FactoryGirl.build(:indc_value, indicator: nil) ).to have(1).errors_on(:indicator) end it 'should be invalid when location not present' do expect( - FactoryGirl.build(:wb_indc_value, location: nil) + FactoryGirl.build(:indc_value, location: nil) ).to have(1).errors_on(:location) end + it 'should be invalid when value not present' do + expect( + FactoryGirl.build(:indc_value, value: nil) + ).to have(1).errors_on(:value) + end + it 'should not be invalid when sector not present' do expect( - FactoryGirl.build(:wb_indc_value, sector: nil) + FactoryGirl.build(:indc_value, sector: nil) ).to have(0).errors_on(:sector) end end diff --git a/spec/models/wb_indc/category_spec.rb b/spec/models/wb_indc/category_spec.rb deleted file mode 100644 index 482f90a9c3..0000000000 --- a/spec/models/wb_indc/category_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'rails_helper' - -describe WbIndc::Category, type: :model do - it 'should be invalid when name not present' do - expect( - FactoryGirl.build(:wb_indc_category, name: nil) - ).to have(1).errors_on(:name) - end -end diff --git a/spec/models/wb_indc/indicator_spec.rb b/spec/models/wb_indc/indicator_spec.rb deleted file mode 100644 index 853b62bfdf..0000000000 --- a/spec/models/wb_indc/indicator_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rails_helper' - -describe WbIndc::Indicator, type: :model do - it 'should be invalid when name not present' do - expect( - FactoryGirl.build(:wb_indc_indicator, name: nil) - ).to have(1).errors_on(:name) - end - - it 'should be invalid when indicator_type not present' do - expect( - FactoryGirl.build(:wb_indc_indicator, indicator_type: nil) - ).to have(1).errors_on(:indicator_type) - end - - it 'should be able to create indicator with all dependant records' do - expect( - FactoryGirl.build(:wb_indc_indicator_with_dependants) - ).to be_valid - end -end diff --git a/spec/models/wb_indc/indicator_type_spec.rb b/spec/models/wb_indc/indicator_type_spec.rb deleted file mode 100644 index deebc466d2..0000000000 --- a/spec/models/wb_indc/indicator_type_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'rails_helper' - -describe WbIndc::IndicatorType, type: :model do - it 'should be invalid when name not present' do - expect( - FactoryGirl.build(:wb_indc_indicator_type, name: nil) - ).to have(1).errors_on(:name) - end -end From c28370d9b920cf90484c73824117b2d7c8903b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Fri, 27 Oct 2017 19:46:58 +0100 Subject: [PATCH 23/43] Fix models and factories so specs work --- app/models/indc/label.rb | 2 +- app/models/indc/source.rb | 4 ++-- app/models/indc/submission.rb | 1 - spec/factories/indc_categories.rb | 1 + spec/factories/indc_indicators.rb | 1 + spec/factories/indc_labels.rb | 2 +- spec/models/indc/indicator_spec.rb | 2 +- spec/models/indc/label_spec.rb | 6 +++--- spec/models/indc/submission_spec.rb | 6 ++++++ 9 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app/models/indc/label.rb b/app/models/indc/label.rb index fe728f6d12..e4d466fbd9 100644 --- a/app/models/indc/label.rb +++ b/app/models/indc/label.rb @@ -2,7 +2,7 @@ module Indc class Label < ApplicationRecord belongs_to :indicator, class_name: 'Indc::Indicator' - validates :name, presence: true + validates :value, presence: true validates :index, presence: true end end diff --git a/app/models/indc/source.rb b/app/models/indc/source.rb index 70e70871fd..5b22cd2cbb 100644 --- a/app/models/indc/source.rb +++ b/app/models/indc/source.rb @@ -1,6 +1,6 @@ module Indc class Source < ApplicationRecord - validate :name, presence: true - validate :name, uniqueness: true + validates :name, presence: true + validates :name, uniqueness: true end end diff --git a/app/models/indc/submission.rb b/app/models/indc/submission.rb index 5d71d19fc7..c5c6604531 100644 --- a/app/models/indc/submission.rb +++ b/app/models/indc/submission.rb @@ -14,4 +14,3 @@ def submission_date=(val) end end end - diff --git a/spec/factories/indc_categories.rb b/spec/factories/indc_categories.rb index 949d633b7c..abc9382cec 100644 --- a/spec/factories/indc_categories.rb +++ b/spec/factories/indc_categories.rb @@ -2,6 +2,7 @@ factory :indc_category, class: 'Indc::Category' do name 'MyName' sequence :slug { |n| "my-slug-" + ('AA'..'ZZ').to_a[n] } + association :source, factory: :indc_source association :category_type, factory: :indc_category_type end end diff --git a/spec/factories/indc_indicators.rb b/spec/factories/indc_indicators.rb index 0014b1344f..f95ce71958 100644 --- a/spec/factories/indc_indicators.rb +++ b/spec/factories/indc_indicators.rb @@ -1,6 +1,7 @@ FactoryGirl.define do factory :indc_indicator, class: 'Indc::Indicator' do name 'MyName' + association :source, factory: :indc_source sequence :slug { |n| "my-slug-" + ('AA'..'ZZ').to_a[n] } trait :with_dependants do diff --git a/spec/factories/indc_labels.rb b/spec/factories/indc_labels.rb index f28ad5b8be..d4051bdd96 100644 --- a/spec/factories/indc_labels.rb +++ b/spec/factories/indc_labels.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :indc_label, class: 'Indc::Label' do association :indicator, factory: :indc_indicator - name 'MyText' + value 'MyLabel' sequence :index { |n| (0..99).to_a[n] } end end diff --git a/spec/models/indc/indicator_spec.rb b/spec/models/indc/indicator_spec.rb index a58422d536..484d64dc5c 100644 --- a/spec/models/indc/indicator_spec.rb +++ b/spec/models/indc/indicator_spec.rb @@ -9,7 +9,7 @@ it 'should be invalid when slug not present' do expect( - FactoryGirl.build(:indc_indicator, sljg: nil) + FactoryGirl.build(:indc_indicator, slug: nil) ).to have(1).errors_on(:slug) end diff --git a/spec/models/indc/label_spec.rb b/spec/models/indc/label_spec.rb index 84df0ae192..44b386e726 100644 --- a/spec/models/indc/label_spec.rb +++ b/spec/models/indc/label_spec.rb @@ -7,10 +7,10 @@ ).to have(1).errors_on(:indicator) end - it 'should be invalid when name not present' do + it 'should be invalid when value not present' do expect( - FactoryGirl.build(:indc_label, name: nil) - ).to have(1).errors_on(:name) + FactoryGirl.build(:indc_label, value: nil) + ).to have(1).errors_on(:value) end it 'should be invalid when index not present' do diff --git a/spec/models/indc/submission_spec.rb b/spec/models/indc/submission_spec.rb index 538a23ee33..5211078405 100644 --- a/spec/models/indc/submission_spec.rb +++ b/spec/models/indc/submission_spec.rb @@ -22,6 +22,12 @@ it 'should be invalid when url not present' do expect( FactoryGirl.build(:indc_submission, url: nil) + ).to have(2).errors_on(:url) + end + + it 'should be invalid when url present but invalid' do + expect( + FactoryGirl.build(:indc_submission, url: 'not an url') ).to have(1).errors_on(:url) end end From 67d9da82e0f8a4630bab42962d9b03f931f8ae25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Mon, 30 Oct 2017 15:19:42 +0000 Subject: [PATCH 24/43] Fix migrations, schema, models and specs --- app/models/cait_indc.rb | 5 - app/models/cait_indc/category.rb | 8 - app/models/cait_indc/chart.rb | 6 - app/models/cait_indc/indicator.rb | 11 - app/models/cait_indc/label.rb | 6 - app/models/cait_indc/submission.rb | 15 - app/models/cait_indc/value.rb | 7 - app/models/global_indc.rb | 5 - app/models/global_indc/category.rb | 16 - app/models/global_indc/indicator.rb | 23 -- app/models/indc/category.rb | 3 +- app/models/indc/sector.rb | 2 - app/models/location.rb | 4 +- app/models/wb_indc.rb | 5 - app/models/wb_indc/category.rb | 7 - app/models/wb_indc/indicator.rb | 10 - app/models/wb_indc/indicator_type.rb | 6 - app/models/wb_indc/sector.rb | 6 - app/models/wb_indc/value.rb | 7 - ...27155954_drop_old_indc_tables_and_views.rb | 2 +- ...171027164658_create_new_indc_categories.rb | 6 +- .../20171027164708_create_new_indc_sectors.rb | 3 - db/schema.rb | 380 ++++++++++++++++++ spec/factories/global_indc_categories.rb | 6 - spec/factories/global_indc_indicators.rb | 15 - spec/factories/indc_categories.rb | 1 - spec/factories/indc_sectors.rb | 1 - spec/models/global_indc/category_spec.rb | 15 - spec/models/global_indc/indicator_spec.rb | 13 - spec/models/indc/sector_spec.rb | 6 - 30 files changed, 385 insertions(+), 215 deletions(-) delete mode 100644 app/models/cait_indc.rb delete mode 100644 app/models/cait_indc/category.rb delete mode 100644 app/models/cait_indc/chart.rb delete mode 100644 app/models/cait_indc/indicator.rb delete mode 100644 app/models/cait_indc/label.rb delete mode 100644 app/models/cait_indc/submission.rb delete mode 100644 app/models/cait_indc/value.rb delete mode 100644 app/models/global_indc.rb delete mode 100644 app/models/global_indc/category.rb delete mode 100644 app/models/global_indc/indicator.rb delete mode 100644 app/models/wb_indc.rb delete mode 100644 app/models/wb_indc/category.rb delete mode 100644 app/models/wb_indc/indicator.rb delete mode 100644 app/models/wb_indc/indicator_type.rb delete mode 100644 app/models/wb_indc/sector.rb delete mode 100644 app/models/wb_indc/value.rb delete mode 100644 spec/factories/global_indc_categories.rb delete mode 100644 spec/factories/global_indc_indicators.rb delete mode 100644 spec/models/global_indc/category_spec.rb delete mode 100644 spec/models/global_indc/indicator_spec.rb diff --git a/app/models/cait_indc.rb b/app/models/cait_indc.rb deleted file mode 100644 index 63f61a3751..0000000000 --- a/app/models/cait_indc.rb +++ /dev/null @@ -1,5 +0,0 @@ -module CaitIndc - def self.table_name_prefix - 'cait_indc_' - end -end diff --git a/app/models/cait_indc/category.rb b/app/models/cait_indc/category.rb deleted file mode 100644 index c1f4cf4453..0000000000 --- a/app/models/cait_indc/category.rb +++ /dev/null @@ -1,8 +0,0 @@ -module CaitIndc - class Category < ApplicationRecord - has_and_belongs_to_many :indicators, - join_table: :cait_indc_indicators_categories - validates :name, presence: true - validates :category_type, inclusion: {in: %w(map overview)} - end -end diff --git a/app/models/cait_indc/chart.rb b/app/models/cait_indc/chart.rb deleted file mode 100644 index c37794ae75..0000000000 --- a/app/models/cait_indc/chart.rb +++ /dev/null @@ -1,6 +0,0 @@ -module CaitIndc - class Chart < ApplicationRecord - has_many :indicators, class_name: 'CaitIndc::Indicator' - validates :name, presence: true - end -end diff --git a/app/models/cait_indc/indicator.rb b/app/models/cait_indc/indicator.rb deleted file mode 100644 index f5f4f93a68..0000000000 --- a/app/models/cait_indc/indicator.rb +++ /dev/null @@ -1,11 +0,0 @@ -module CaitIndc - class Indicator < ApplicationRecord - belongs_to :chart, class_name: 'CaitIndc::Chart', optional: true - has_and_belongs_to_many :categories, - join_table: :cait_indc_indicators_categories - has_many :labels, class_name: 'CaitIndc::Label' - has_many :values, class_name: 'CaitIndc::Value' - - validates :name, presence: true - end -end diff --git a/app/models/cait_indc/label.rb b/app/models/cait_indc/label.rb deleted file mode 100644 index 49c9b7a727..0000000000 --- a/app/models/cait_indc/label.rb +++ /dev/null @@ -1,6 +0,0 @@ -module CaitIndc - class Label < ApplicationRecord - belongs_to :indicator, class_name: 'CaitIndc::Indicator' - validates :name, presence: true - end -end diff --git a/app/models/cait_indc/submission.rb b/app/models/cait_indc/submission.rb deleted file mode 100644 index 5d509d60c1..0000000000 --- a/app/models/cait_indc/submission.rb +++ /dev/null @@ -1,15 +0,0 @@ -module CaitIndc - class Submission < ApplicationRecord - belongs_to :location - validates :submission_type, presence: true - validates :language, presence: true - validates :submission_date, presence: true - validates :url, - presence: true, - format: URI.regexp(%w(http https)) - - def submission_date=(val) - write_attribute :submission_date, Date.strptime(val, '%m/%d/%Y') - end - end -end diff --git a/app/models/cait_indc/value.rb b/app/models/cait_indc/value.rb deleted file mode 100644 index fa639ddd94..0000000000 --- a/app/models/cait_indc/value.rb +++ /dev/null @@ -1,7 +0,0 @@ -module CaitIndc - class Value < ApplicationRecord - belongs_to :location - belongs_to :indicator, class_name: 'CaitIndc::Indicator' - belongs_to :label, class_name: 'CaitIndc::Label', optional: true - end -end diff --git a/app/models/global_indc.rb b/app/models/global_indc.rb deleted file mode 100644 index 590fb2f1cf..0000000000 --- a/app/models/global_indc.rb +++ /dev/null @@ -1,5 +0,0 @@ -module GlobalIndc - def self.table_name_prefix - 'global_indc_' - end -end diff --git a/app/models/global_indc/category.rb b/app/models/global_indc/category.rb deleted file mode 100644 index aaf35cc334..0000000000 --- a/app/models/global_indc/category.rb +++ /dev/null @@ -1,16 +0,0 @@ -module GlobalIndc - class Category < ApplicationRecord - has_and_belongs_to_many :indicators, - join_table: :global_indc_indicators_categories - belongs_to :parent, - class_name: 'GlobalIndc::Category', - foreign_key: 'parent_id', - required: false - has_many :children, - class_name: 'GlobalIndc::Category', - foreign_key: 'parent_id' - - validates :name, presence: true - validates :slug, presence: true - end -end diff --git a/app/models/global_indc/indicator.rb b/app/models/global_indc/indicator.rb deleted file mode 100644 index fab114cbb3..0000000000 --- a/app/models/global_indc/indicator.rb +++ /dev/null @@ -1,23 +0,0 @@ -module GlobalIndc - class Indicator < ApplicationRecord - has_and_belongs_to_many :categories, - join_table: :global_indc_indicators_categories - belongs_to :cait_indicator, - class_name: 'CaitIndc::Indicator', - required: false - belongs_to :wb_indicator, - class_name: 'WbIndc::Indicator', - required: false - - validate :cait_xor_wb_indicator_presence - - def cait_xor_wb_indicator_presence - return if cait_indicator.blank? ^ wb_indicator.blank? - - errors.add( - :base, - 'Reference either a cait or wb indicator, never both or none' - ) - end - end -end diff --git a/app/models/indc/category.rb b/app/models/indc/category.rb index a1d0ac9620..e6490bf6ed 100644 --- a/app/models/indc/category.rb +++ b/app/models/indc/category.rb @@ -1,12 +1,11 @@ module Indc class Category < ApplicationRecord - belongs_to :source, class_name: 'Indc::Source' belongs_to :category_type, class_name: 'Indc::CategoryType' has_and_belongs_to_many :indicators, join_table: :indc_indicators_categories validates :slug, presence: true validates :name, presence: true - validates :slug, uniqueness: true + validates :slug, uniqueness: {scope: :category_type} end end diff --git a/app/models/indc/sector.rb b/app/models/indc/sector.rb index a9d230f939..9e4dcd5964 100644 --- a/app/models/indc/sector.rb +++ b/app/models/indc/sector.rb @@ -3,8 +3,6 @@ class Sector < ApplicationRecord belongs_to :parent, class_name: 'Indc::Sector', optional: true has_many :values, class_name: 'Indc::Value' - validates :slug, presence: true validates :name, presence: true - validates :slug, uniqueness: true end end diff --git a/app/models/location.rb b/app/models/location.rb index 240e3b8b83..fd7eaf4b57 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -5,9 +5,9 @@ class Location < ApplicationRecord has_many :historical_emissions, class_name: 'HistoricalEmissions::Record', dependent: :destroy - has_many :values, class_name: 'CaitIndc::Value' + has_many :values, class_name: 'Indc::Value' has_many :indicators, - class_name: 'CaitIndc::Indicator', + class_name: 'Indc::Indicator', through: :values validates :iso_code3, presence: true, uniqueness: true diff --git a/app/models/wb_indc.rb b/app/models/wb_indc.rb deleted file mode 100644 index 556d870edc..0000000000 --- a/app/models/wb_indc.rb +++ /dev/null @@ -1,5 +0,0 @@ -module WbIndc - def self.table_name_prefix - 'wb_indc_' - end -end diff --git a/app/models/wb_indc/category.rb b/app/models/wb_indc/category.rb deleted file mode 100644 index 5193a025d5..0000000000 --- a/app/models/wb_indc/category.rb +++ /dev/null @@ -1,7 +0,0 @@ -module WbIndc - class Category < ApplicationRecord - has_and_belongs_to_many :indicators, - join_table: :wb_indc_indicators_categories - validates :name, presence: true - end -end diff --git a/app/models/wb_indc/indicator.rb b/app/models/wb_indc/indicator.rb deleted file mode 100644 index eed4dd2dc9..0000000000 --- a/app/models/wb_indc/indicator.rb +++ /dev/null @@ -1,10 +0,0 @@ -module WbIndc - class Indicator < ApplicationRecord - belongs_to :indicator_type, class_name: 'WbIndc::IndicatorType' - has_many :values, class_name: 'WbIndc::Value' - has_and_belongs_to_many :categories, - join_table: :wb_indc_indicators_categories - validates :name, presence: true - validates :code, presence: true - end -end diff --git a/app/models/wb_indc/indicator_type.rb b/app/models/wb_indc/indicator_type.rb deleted file mode 100644 index 62541255df..0000000000 --- a/app/models/wb_indc/indicator_type.rb +++ /dev/null @@ -1,6 +0,0 @@ -module WbIndc - class IndicatorType < ApplicationRecord - has_many :indicators, class_name: 'WbIndc::Indicator' - validates :name, presence: true - end -end diff --git a/app/models/wb_indc/sector.rb b/app/models/wb_indc/sector.rb deleted file mode 100644 index 6a7a27c46f..0000000000 --- a/app/models/wb_indc/sector.rb +++ /dev/null @@ -1,6 +0,0 @@ -module WbIndc - class Sector < ApplicationRecord - belongs_to :parent, class_name: 'WbIndc::Sector', optional: true - validates :name, presence: true - end -end diff --git a/app/models/wb_indc/value.rb b/app/models/wb_indc/value.rb deleted file mode 100644 index 387b3f2782..0000000000 --- a/app/models/wb_indc/value.rb +++ /dev/null @@ -1,7 +0,0 @@ -module WbIndc - class Value < ApplicationRecord - belongs_to :location - belongs_to :sector, class_name: 'WbIndc::Sector', optional: true - belongs_to :indicator, class_name: 'WbIndc::Indicator' - end -end diff --git a/db/migrate/20171027155954_drop_old_indc_tables_and_views.rb b/db/migrate/20171027155954_drop_old_indc_tables_and_views.rb index bf8e70241d..c262b8da4d 100644 --- a/db/migrate/20171027155954_drop_old_indc_tables_and_views.rb +++ b/db/migrate/20171027155954_drop_old_indc_tables_and_views.rb @@ -7,9 +7,9 @@ def change drop_view :indc_indicators, materialized: true drop_view :indc_categories, materialized: true + drop_table :global_indc_indicators_categories drop_table :global_indc_categories drop_table :global_indc_indicators - drop_table :global_indc_indicators_categories drop_table :cait_indc_values drop_table :cait_indc_labels diff --git a/db/migrate/20171027164658_create_new_indc_categories.rb b/db/migrate/20171027164658_create_new_indc_categories.rb index bd62481253..76ce5a1952 100644 --- a/db/migrate/20171027164658_create_new_indc_categories.rb +++ b/db/migrate/20171027164658_create_new_indc_categories.rb @@ -1,10 +1,6 @@ class CreateNewIndcCategories < ActiveRecord::Migration[5.1] def change create_table :indc_categories do |t| - t.references :source, foreign_key: { - to_table: :indc_sources, - on_delete: :cascade - }, null: false t.references :category_type, foreign_key: { to_table: :indc_category_types, on_delete: :cascade @@ -13,6 +9,6 @@ def change t.text :name, null: false t.timestamps end - add_index :indc_categories, :slug, unique: true + add_index :indc_categories, [:slug, :category_type_id], unique: true end end diff --git a/db/migrate/20171027164708_create_new_indc_sectors.rb b/db/migrate/20171027164708_create_new_indc_sectors.rb index 1a5898ba16..4ddedc15e8 100644 --- a/db/migrate/20171027164708_create_new_indc_sectors.rb +++ b/db/migrate/20171027164708_create_new_indc_sectors.rb @@ -6,10 +6,7 @@ def change on_delete: :cascade } t.text :name, null: false - t.text :slug, null: false t.timestamps end - - add_index :indc_sectors, :slug, unique: true end end diff --git a/db/schema.rb b/db/schema.rb index e69de29bb2..aced25991b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -0,0 +1,380 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20171027170320) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "adaptation_values", force: :cascade do |t| + t.bigint "variable_id" + t.bigint "location_id" + t.text "string_value" + t.float "number_value" + t.boolean "boolean_value" + t.integer "absolute_rank" + t.float "relative_rank" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["location_id"], name: "index_adaptation_values_on_location_id" + t.index ["variable_id"], name: "index_adaptation_values_on_variable_id" + end + + create_table "adaptation_variables", force: :cascade do |t| + t.text "slug" + t.text "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["slug"], name: "index_adaptation_variables_on_slug", unique: true + end + + create_table "historical_emissions_data_sources", force: :cascade do |t| + t.text "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "historical_emissions_gases", force: :cascade do |t| + t.text "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "historical_emissions_gwps", force: :cascade do |t| + t.text "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "historical_emissions_records", force: :cascade do |t| + t.bigint "location_id" + t.bigint "data_source_id" + t.bigint "sector_id" + t.bigint "gas_id" + t.jsonb "emissions" + t.bigint "gwp_id" + t.index ["data_source_id"], name: "index_historical_emissions_records_on_data_source_id" + t.index ["gas_id"], name: "index_historical_emissions_records_on_gas_id" + t.index ["gwp_id"], name: "index_historical_emissions_records_on_gwp_id" + t.index ["location_id"], name: "index_historical_emissions_records_on_location_id" + t.index ["sector_id"], name: "index_historical_emissions_records_on_sector_id" + end + + create_table "historical_emissions_sectors", force: :cascade do |t| + t.bigint "parent_id" + t.bigint "data_source_id" + t.text "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "annex_type" + t.index ["data_source_id"], name: "index_historical_emissions_sectors_on_data_source_id" + t.index ["parent_id"], name: "index_historical_emissions_sectors_on_parent_id" + end + + create_table "indc_categories", force: :cascade do |t| + t.bigint "category_type_id", null: false + t.text "slug", null: false + t.text "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["category_type_id"], name: "index_indc_categories_on_category_type_id" + t.index ["slug", "category_type_id"], name: "index_indc_categories_on_slug_and_category_type_id", unique: true + end + + create_table "indc_category_types", force: :cascade do |t| + t.text "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["name"], name: "index_indc_category_types_on_name", unique: true + end + + create_table "indc_indicators", force: :cascade do |t| + t.bigint "source_id", null: false + t.text "slug", null: false + t.text "name", null: false + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["slug"], name: "index_indc_indicators_on_slug", unique: true + t.index ["source_id"], name: "index_indc_indicators_on_source_id" + end + + create_table "indc_indicators_categories", force: :cascade do |t| + t.bigint "indicator_id", null: false + t.bigint "category_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["category_id"], name: "index_indc_indicators_categories_on_category_id" + t.index ["indicator_id", "category_id"], name: "indc_indicators_categories_uniq", unique: true + t.index ["indicator_id"], name: "index_indc_indicators_categories_on_indicator_id" + end + + create_table "indc_labels", force: :cascade do |t| + t.bigint "indicator_id", null: false + t.text "value", null: false + t.integer "index", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["indicator_id"], name: "index_indc_labels_on_indicator_id" + end + + create_table "indc_sectors", force: :cascade do |t| + t.bigint "parent_id" + t.text "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["parent_id"], name: "index_indc_sectors_on_parent_id" + end + + create_table "indc_sources", force: :cascade do |t| + t.text "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["name"], name: "index_indc_sources_on_name", unique: true + end + + create_table "indc_submissions", force: :cascade do |t| + t.bigint "location_id", null: false + t.text "submission_type", null: false + t.text "language", null: false + t.date "submission_date", null: false + t.text "url", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["location_id"], name: "index_indc_submissions_on_location_id" + end + + create_table "indc_values", force: :cascade do |t| + t.bigint "indicator_id", null: false + t.bigint "location_id", null: false + t.bigint "sector_id" + t.bigint "label_id" + t.text "value", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["indicator_id"], name: "index_indc_values_on_indicator_id" + t.index ["label_id"], name: "index_indc_values_on_label_id" + t.index ["location_id"], name: "index_indc_values_on_location_id" + t.index ["sector_id"], name: "index_indc_values_on_sector_id" + end + + create_table "location_members", force: :cascade do |t| + t.bigint "location_id" + t.bigint "member_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["location_id"], name: "index_location_members_on_location_id" + t.index ["member_id"], name: "index_location_members_on_member_id" + end + + create_table "locations", force: :cascade do |t| + t.text "iso_code3", null: false + t.text "pik_name" + t.text "cait_name" + t.text "ndcp_navigators_name" + t.text "wri_standard_name", null: false + t.text "unfccc_group" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "iso_code2", null: false + t.text "location_type", null: false + t.boolean "show_in_cw", default: true + t.json "topojson" + t.jsonb "centroid" + t.index ["iso_code2"], name: "index_locations_on_iso_code2" + t.index ["iso_code3"], name: "index_locations_on_iso_code3" + end + + create_table "ndc_sdg_goals", force: :cascade do |t| + t.text "number", null: false + t.text "title", null: false + t.text "cw_title", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "colour", null: false + t.index ["number"], name: "index_ndc_sdg_goals_on_number", unique: true + end + + create_table "ndc_sdg_ndc_target_sectors", force: :cascade do |t| + t.bigint "ndc_target_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "sector_id" + t.index ["ndc_target_id"], name: "index_ndc_sdg_ndc_target_sectors_on_ndc_target_id" + t.index ["sector_id"], name: "index_ndc_sdg_ndc_target_sectors_on_sector_id" + end + + create_table "ndc_sdg_ndc_targets", force: :cascade do |t| + t.bigint "ndc_id" + t.bigint "target_id" + t.text "indc_text" + t.text "status" + t.text "climate_response" + t.text "type_of_information" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["ndc_id"], name: "index_ndc_sdg_ndc_targets_on_ndc_id" + t.index ["target_id"], name: "index_ndc_sdg_ndc_targets_on_target_id" + end + + create_table "ndc_sdg_sectors", force: :cascade do |t| + t.text "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "ndc_sdg_targets", force: :cascade do |t| + t.text "number", null: false + t.text "title", null: false + t.bigint "goal_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["goal_id"], name: "index_ndc_sdg_targets_on_goal_id" + t.index ["number"], name: "index_ndc_sdg_targets_on_number", unique: true + end + + create_table "ndcs", force: :cascade do |t| + t.bigint "location_id" + t.text "full_text" + t.tsvector "full_text_tsv" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "document_type", default: "ndc" + t.text "language" + t.index ["full_text_tsv"], name: "index_ndcs_on_full_text_tsv", using: :gin + t.index ["location_id"], name: "index_ndcs_on_location_id" + end + + create_table "quantification_labels", force: :cascade do |t| + t.text "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["name"], name: "index_quantification_labels_on_name", unique: true + end + + create_table "quantification_values", force: :cascade do |t| + t.bigint "location_id" + t.bigint "label_id" + t.integer "year" + t.float "value" + t.boolean "range" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["label_id"], name: "index_quantification_values_on_label_id" + t.index ["location_id"], name: "index_quantification_values_on_location_id" + end + + create_table "timeline_documents", force: :cascade do |t| + t.bigint "source_id" + t.bigint "location_id" + t.text "link" + t.text "text" + t.date "date" + t.text "language" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["location_id"], name: "index_timeline_documents_on_location_id" + t.index ["source_id"], name: "index_timeline_documents_on_source_id" + end + + create_table "timeline_notes", force: :cascade do |t| + t.bigint "document_id" + t.text "note" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["document_id"], name: "index_timeline_notes_on_document_id" + end + + create_table "timeline_sources", force: :cascade do |t| + t.text "name" + end + + create_table "wb_extra_country_data", force: :cascade do |t| + t.bigint "location_id" + t.integer "year" + t.bigint "gdp" + t.bigint "population" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["location_id"], name: "index_wb_extra_country_data_on_location_id" + end + + create_table "wri_metadata_acronyms", force: :cascade do |t| + t.text "acronym" + t.text "definition" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["acronym"], name: "index_wri_metadata_acronyms_on_acronym", unique: true + end + + create_table "wri_metadata_properties", force: :cascade do |t| + t.text "slug" + t.text "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["slug"], name: "index_wri_metadata_properties_on_slug", unique: true + end + + create_table "wri_metadata_sources", force: :cascade do |t| + t.text "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "wri_metadata_values", force: :cascade do |t| + t.bigint "source_id" + t.bigint "property_id" + t.text "value" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["property_id"], name: "index_wri_metadata_values_on_property_id" + t.index ["source_id", "property_id"], name: "source_id_property_id_index", unique: true + t.index ["source_id"], name: "index_wri_metadata_values_on_source_id" + end + + add_foreign_key "adaptation_values", "adaptation_variables", column: "variable_id", on_delete: :cascade + add_foreign_key "adaptation_values", "locations", on_delete: :cascade + add_foreign_key "historical_emissions_records", "historical_emissions_data_sources", column: "data_source_id", on_delete: :cascade + add_foreign_key "historical_emissions_records", "historical_emissions_gases", column: "gas_id", on_delete: :cascade + add_foreign_key "historical_emissions_records", "historical_emissions_gwps", column: "gwp_id" + add_foreign_key "historical_emissions_records", "historical_emissions_sectors", column: "sector_id", on_delete: :cascade + add_foreign_key "historical_emissions_records", "locations", on_delete: :cascade + add_foreign_key "historical_emissions_sectors", "historical_emissions_data_sources", column: "data_source_id", on_delete: :cascade + add_foreign_key "historical_emissions_sectors", "historical_emissions_sectors", column: "parent_id", on_delete: :cascade + add_foreign_key "indc_categories", "indc_category_types", column: "category_type_id", on_delete: :cascade + add_foreign_key "indc_indicators", "indc_sources", column: "source_id", on_delete: :cascade + add_foreign_key "indc_indicators_categories", "indc_categories", column: "category_id", on_delete: :cascade + add_foreign_key "indc_indicators_categories", "indc_indicators", column: "indicator_id", on_delete: :cascade + add_foreign_key "indc_labels", "indc_indicators", column: "indicator_id", on_delete: :cascade + add_foreign_key "indc_sectors", "indc_sectors", column: "parent_id", on_delete: :cascade + add_foreign_key "indc_submissions", "locations", on_delete: :cascade + add_foreign_key "indc_values", "indc_indicators", column: "indicator_id", on_delete: :cascade + add_foreign_key "indc_values", "indc_labels", column: "label_id", on_delete: :cascade + add_foreign_key "indc_values", "indc_sectors", column: "sector_id", on_delete: :cascade + add_foreign_key "indc_values", "locations", on_delete: :cascade + add_foreign_key "location_members", "locations", column: "member_id", on_delete: :cascade + add_foreign_key "location_members", "locations", on_delete: :cascade + add_foreign_key "ndc_sdg_ndc_target_sectors", "ndc_sdg_ndc_targets", column: "ndc_target_id", on_delete: :cascade + add_foreign_key "ndc_sdg_ndc_target_sectors", "ndc_sdg_sectors", column: "sector_id", on_delete: :cascade + add_foreign_key "ndc_sdg_ndc_targets", "ndc_sdg_targets", column: "target_id", on_delete: :cascade + add_foreign_key "ndc_sdg_ndc_targets", "ndcs", on_delete: :cascade + add_foreign_key "ndc_sdg_targets", "ndc_sdg_goals", column: "goal_id" + add_foreign_key "ndcs", "locations", on_delete: :cascade + add_foreign_key "quantification_values", "locations", on_delete: :cascade + add_foreign_key "quantification_values", "quantification_labels", column: "label_id", on_delete: :cascade + add_foreign_key "timeline_documents", "locations", on_delete: :cascade + add_foreign_key "timeline_documents", "timeline_sources", column: "source_id", on_delete: :cascade + add_foreign_key "timeline_notes", "timeline_documents", column: "document_id", on_delete: :cascade + add_foreign_key "wb_extra_country_data", "locations", on_delete: :cascade + add_foreign_key "wri_metadata_values", "wri_metadata_properties", column: "property_id", on_delete: :cascade + add_foreign_key "wri_metadata_values", "wri_metadata_sources", column: "source_id", on_delete: :cascade +end diff --git a/spec/factories/global_indc_categories.rb b/spec/factories/global_indc_categories.rb deleted file mode 100644 index fe20a0bccb..0000000000 --- a/spec/factories/global_indc_categories.rb +++ /dev/null @@ -1,6 +0,0 @@ -FactoryGirl.define do - factory :global_indc_category, class: 'GlobalIndc::Category' do - name 'MyText' - slug 'my-text' - end -end diff --git a/spec/factories/global_indc_indicators.rb b/spec/factories/global_indc_indicators.rb deleted file mode 100644 index e2175ff71e..0000000000 --- a/spec/factories/global_indc_indicators.rb +++ /dev/null @@ -1,15 +0,0 @@ -FactoryGirl.define do - factory :global_indc_indicator, class: 'GlobalIndc::Indicator' do - trait :with_cait_reference do - after(:create) do |indicator| - indicator.cait_indicator = create(:cait_indc_indicator) - end - end - - trait :with_wb_reference do - after(:create) do |indicator| - indicator.wb_indicator = create(:wb_indc_indicator) - end - end - end -end diff --git a/spec/factories/indc_categories.rb b/spec/factories/indc_categories.rb index abc9382cec..949d633b7c 100644 --- a/spec/factories/indc_categories.rb +++ b/spec/factories/indc_categories.rb @@ -2,7 +2,6 @@ factory :indc_category, class: 'Indc::Category' do name 'MyName' sequence :slug { |n| "my-slug-" + ('AA'..'ZZ').to_a[n] } - association :source, factory: :indc_source association :category_type, factory: :indc_category_type end end diff --git a/spec/factories/indc_sectors.rb b/spec/factories/indc_sectors.rb index 48dffbc478..6f4af6dc8b 100644 --- a/spec/factories/indc_sectors.rb +++ b/spec/factories/indc_sectors.rb @@ -1,6 +1,5 @@ FactoryGirl.define do factory :indc_sector, class: 'Indc::Sector' do name 'MyName' - sequence :slug { |n| "my-slug-" + ('AA'..'ZZ').to_a[n] } end end diff --git a/spec/models/global_indc/category_spec.rb b/spec/models/global_indc/category_spec.rb deleted file mode 100644 index 3d35a04798..0000000000 --- a/spec/models/global_indc/category_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'rails_helper' - -RSpec.describe GlobalIndc::Category, type: :model do - it 'should be invalid when name not present' do - expect( - FactoryGirl.build(:global_indc_category, name: nil) - ).to have(1).errors_on(:name) - end - - it 'should be invalid when slug not present' do - expect( - FactoryGirl.build(:global_indc_category, slug: nil) - ).to have(1).errors_on(:slug) - end -end diff --git a/spec/models/global_indc/indicator_spec.rb b/spec/models/global_indc/indicator_spec.rb deleted file mode 100644 index 7ad8a69d4e..0000000000 --- a/spec/models/global_indc/indicator_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'rails_helper' - -RSpec.describe GlobalIndc::Indicator, type: :model do - it 'should be invalid when referencing both cait and wb indicators' do - expect( - FactoryGirl.build( - :global_indc_indicator, - :with_cait_reference, - :with_wb_reference - ) - ).to have(1).errors_on(:base) - end -end diff --git a/spec/models/indc/sector_spec.rb b/spec/models/indc/sector_spec.rb index c878c7c9ae..b3597a4902 100644 --- a/spec/models/indc/sector_spec.rb +++ b/spec/models/indc/sector_spec.rb @@ -6,10 +6,4 @@ FactoryGirl.build(:indc_sector, name: nil) ).to have(1).errors_on(:name) end - - it 'should be invalid when slug not present' do - expect( - FactoryGirl.build(:indc_sector, slug: nil) - ).to have(1).errors_on(:slug) - end end From d4d7cbdb973e916afebfe781fd61c2d9e3f3c93d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Mon, 30 Oct 2017 20:36:59 +0000 Subject: [PATCH 25/43] Add new indc import process Also remove old files --- app/services/import_cait_indc.rb | 231 ------------------- app/services/import_global_indc.rb | 81 ------- app/services/import_indc.rb | 272 +++++++++++++++++++++++ app/services/import_wb_indc.rb | 159 ------------- lib/tasks/db.rake | 3 +- lib/tasks/indc.rake | 7 + spec/services/import_cait_indc_spec.rb | 70 ------ spec/services/import_global_indc_spec.rb | 104 --------- spec/services/import_indc.rb | 116 ++++++++++ spec/services/import_wb_indc_spec.rb | 62 ------ 10 files changed, 396 insertions(+), 709 deletions(-) delete mode 100644 app/services/import_cait_indc.rb delete mode 100644 app/services/import_global_indc.rb create mode 100644 app/services/import_indc.rb delete mode 100644 app/services/import_wb_indc.rb create mode 100644 lib/tasks/indc.rake delete mode 100644 spec/services/import_cait_indc_spec.rb delete mode 100644 spec/services/import_global_indc_spec.rb create mode 100644 spec/services/import_indc.rb delete mode 100644 spec/services/import_wb_indc_spec.rb diff --git a/app/services/import_cait_indc.rb b/app/services/import_cait_indc.rb deleted file mode 100644 index 72bcbf586e..0000000000 --- a/app/services/import_cait_indc.rb +++ /dev/null @@ -1,231 +0,0 @@ -class ImportCaitIndc - # rubocop:disable LineLength - META_INDICATORS_FILEPATH = "#{CW_FILES_PREFIX}cait_indc/CW_NDC_CAIT_metadata.csv".freeze - META_LEGEND_FILEPATH = "#{CW_FILES_PREFIX}cait_indc/CW_NDC_CAIT_legend.csv".freeze - META_MAP_FILEPATH = "#{CW_FILES_PREFIX}cait_indc/CW_NDC_map.csv".freeze - DATA_FILEPATH = "#{CW_FILES_PREFIX}cait_indc/CW_NDC_CAIT_data.csv".freeze - SUBMISSIONS_FILEPATH = "#{CW_FILES_PREFIX}cait_indc/CW_NDC_Submission_URL.csv".freeze - # rubocop:enable LineLength - - def call - cleanup - - load_csvs - - import_categories(@indicators, 'overview') - import_categories(@map, 'map') - import_charts - import_indicators - import_association_indicators_categories - - load_indicator_keys - import_labels - update_label_indexes - import_values - import_submissions - - refresh_materialized_views - end - - private - - def load_csvs - @indicators = S3CSVReader.read(META_INDICATORS_FILEPATH).map(&:to_h) - @legend = S3CSVReader.read(META_LEGEND_FILEPATH).map(&:to_h) - @data = S3CSVReader.read(DATA_FILEPATH).map(&:to_h) - @map = S3CSVReader.read(META_MAP_FILEPATH).map(&:to_h) - @submissions = S3CSVReader.read(SUBMISSIONS_FILEPATH).map(&:to_h) - end - - def load_indicator_keys - @indicator_keys = CaitIndc::Indicator.all.map do |i| - i.slug.to_sym - end - end - - def cleanup - CaitIndc::Value.delete_all - CaitIndc::Label.delete_all - CaitIndc::Indicator.delete_all - CaitIndc::Chart.delete_all - CaitIndc::Category.delete_all - CaitIndc::Submission.delete_all - end - - def chart(indicator) - chart_name = @legend. - detect { |l| l[:indicator_name] == indicator[:column_name] }&. - fetch(:chart_title, nil) - - CaitIndc::Chart.find_by(name: chart_name) if chart_name - end - - def indicator_attributes(indicator) - { - chart: chart(indicator), - name: indicator[:long_name], - slug: indicator[:column_name] - } - end - - def label_attributes(label) - { - indicator: CaitIndc::Indicator. - find_by!(slug: label[:indicator_name]), - name: label[:legend_item_name] - } - end - - def value_attributes(datum, location, indicator_key) - indicator = CaitIndc::Indicator.find_by(slug: indicator_key) - - { - location: location, - indicator: indicator, - label: CaitIndc::Label.find_by( - name: datum[:"#{indicator_key}_label"], - indicator: indicator - ), - value: datum[indicator_key] - } - end - - def category_attributes(category_name, category_type) - { - name: category_name, - slug: Slug.create(category_name), - category_type: category_type - } - end - - def submission_attributes(submission) - { - location: Location.find_by(iso_code3: submission[:iso]), - submission_type: submission[:type], - language: submission[:language], - submission_date: submission[:date_of_submission], - url: submission[:url] - } - end - - def select_categories(dataset, indicator_key, indicator, category_type) - dataset.select { |r| r[indicator_key] == indicator.slug }. - map do |r| - CaitIndc::Category.where( - name: r[:category], - category_type: category_type - ).first - end.uniq - end - - def import_categories(dataset, category_type) - dataset. - map { |r| r[:category].strip }. - uniq. - select(&:itself). - each do |cat| - CaitIndc::Category.create!(category_attributes(cat, category_type)) - end - end - - def import_charts - @legend. - map { |r| r[:chart_title].strip }. - uniq. - select(&:itself). - each { |chart_t| CaitIndc::Chart.create!(name: chart_t) } - end - - def import_indicators - @indicators. - reject { |ind| ind[:column_name].match(/_label$/) }. - each do |ind| - CaitIndc::Indicator.create!(indicator_attributes(ind)) - end - end - - def import_association_indicators_categories - CaitIndc::Indicator.all.each do |indicator| - overview_categories = select_categories( - @indicators, :column_name, indicator, 'overview' - ) - - map_categories = select_categories( - @map, :indicator, indicator, 'map' - ) - - indicator.categories = overview_categories + map_categories - end - end - - def import_labels - label_fields = @data.first.keys.grep(/_label$/). - map { |l| l.to_s.gsub(/_label$/, '') } - - label_accumulator = [] - label_fields.each do |lf| - @data.each do |d| - next if d[:"#{lf}_label"].nil? - - label_accumulator << { - indicator_name: lf.to_s, - legend_item_name: d[:"#{lf}_label"] - } - end - end - - label_accumulator.uniq.each do |l| - CaitIndc::Label.create!(label_attributes(l)) - end - end - - def import_values - ind_keys_no_label = @indicator_keys - @indicator_keys.grep(/_label$/) - @data.each do |d| - location = Location.find_by(iso_code3: d[:iso]) - unless location - Rails.logger.error "location #{d[:country]} not found. Skipping." - next - end - - ind_keys_no_label.select { |ind_k| d[ind_k] }.each do |ind_k| - CaitIndc::Value.create!(value_attributes(d, location, ind_k)) - end - end - end - - def import_submissions - @submissions.each do |sub| - CaitIndc::Submission.create!(submission_attributes(sub)) - end - end - - def update_label_indexes - sql = <<~END - WITH indexes AS ( - SELECT l.id, l.indicator_id, - ROW_NUMBER() OVER ( - PARTITION BY l.indicator_id - ORDER BY l.id asc - ) AS index - FROM cait_indc_labels l - ) - UPDATE cait_indc_labels l - SET index = indexes.index - FROM indexes - WHERE indexes.id = l.id - END - - ActiveRecord::Base.connection.execute(sql) - end - - def refresh_materialized_views - MaterializedView.refresh( - 'indc_categories', - 'indc_indicators', - 'indc_indicators_categories', - 'indc_labels', - 'indc_values' - ) - end -end diff --git a/app/services/import_global_indc.rb b/app/services/import_global_indc.rb deleted file mode 100644 index d2fc3028ea..0000000000 --- a/app/services/import_global_indc.rb +++ /dev/null @@ -1,81 +0,0 @@ -class ImportGlobalIndc - GLOBAL_METADATA_FILEPATH = - "#{CW_FILES_PREFIX}global_indc/CW_NDC_metadata_combined.csv".freeze - - def call - cleanup - - load_csvs - - import_categories - import_indicators - end - - private - - def load_csvs - @metadata = S3CSVReader.read(GLOBAL_METADATA_FILEPATH).map(&:to_h) - @categories_index = {} - end - - def cleanup - GlobalIndc::Category.delete_all - GlobalIndc::Indicator.delete_all - end - - def import_categories - categories.map(&:first).uniq.each do |category| - @categories_index[category] = GlobalIndc::Category.create!( - name: category, - slug: Slug.create(category) - ) - end - - categories.map.uniq(&:second).each do |category| - next if category.second.nil? - @categories_index[category.second] = GlobalIndc::Category.create!( - name: category.second, - slug: Slug.create(category.second), - parent: @categories_index[category.first] - ) - end - end - - def import_indicators - @metadata.each do |row| - next if row[:column_name].nil? || row[:source].nil? || - row[:category_2] == 'NULL' - - indicator = GlobalIndc::Indicator.find_or_create_by!( - indicator(row[:column_name], row[:source]) - ) - - indicator.categories << - if row[:category_2].nil? - @categories_index[row[:category]] - else - @categories_index[row[:category_2]] - end - end - end - - def categories - categories = @metadata.map do |row| - [row[:category], row[:category_2]] - end - - categories = categories.reject do |category| - category.second == 'NULL' - end - - categories.uniq - end - - def indicator(code, source) - if source.casecmp('CAIT').zero? - {cait_indicator: CaitIndc::Indicator.find_by(slug: code)} - elsif source.casecmp('WB').zero? - {wb_indicator: WbIndc::Indicator.find_by(code: code)} - end - end -end diff --git a/app/services/import_indc.rb b/app/services/import_indc.rb new file mode 100644 index 0000000000..e3b5b75c0a --- /dev/null +++ b/app/services/import_indc.rb @@ -0,0 +1,272 @@ +class ImportIndc + DATA_CAIT_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_CAIT_data.csv". + freeze + LEGEND_CAIT_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_CAIT_legend.csv".freeze + DATA_WB_WIDE_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_WB_data_wide.csv".freeze + DATA_WB_SECTORAL_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_WB_data_sectoral.csv".freeze + SUBMISSIONS_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_submission.csv".freeze + METADATA_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_metadata.csv".freeze + + def call + cleanup + + load_csvs + load_locations + + import_sources + import_category_types + import_categories + import_indicators + import_indicators_categories + import_labels + import_values_cait + import_sectors + import_values_wb + import_submissions + end + + private + + def cleanup + Indc::Value.delete_all + Indc::Category.delete_all + Indc::CategoryType.delete_all + Indc::Sector.delete_all + Indc::Label.delete_all + Indc::Indicator.delete_all + Indc::Source.delete_all + Indc::Submission.delete_all + end + + def load_csvs + @cait_data = S3CSVReader.read(DATA_CAIT_FILEPATH).map(&:to_h) + @cait_labels = S3CSVReader.read(LEGEND_CAIT_FILEPATH).map(&:to_h) + @wb_wide_data = S3CSVReader.read(DATA_WB_WIDE_FILEPATH).map(&:to_h) + @wb_sectoral_data = S3CSVReader.read(DATA_WB_SECTORAL_FILEPATH).map(&:to_h) + @metadata = S3CSVReader.read(METADATA_FILEPATH).map(&:to_h) + @submissions = S3CSVReader.read(SUBMISSIONS_FILEPATH).map(&:to_h) + end + + def load_locations + @locations_by_iso3 = Location.all. + group_by(&:iso_code3). + map { |key, value| [key, value.first] }. + to_h + + @locations_by_iso2 = Location.all. + group_by(&:iso_code2). + map { |key, value| [key, value.first] }. + to_h + end + + def category_attributes(name, category_type) + { + name: name, + slug: Slug.create(name), + category_type: category_type + } + end + + def indicator_attributes(indicator) + { + name: indicator[:column_name], + slug: Slug.create("#{indicator[:source]}_#{indicator[:column_name]}"), + description: indicator[:long_name], + source: @sources_index[indicator[:source]] + } + end + + def value_cait_attributes(row, location, indicator) + { + location: location, + indicator: indicator, + label: Indc::Label.find_by( + value: row[:"#{indicator.name}_label"], + indicator: indicator + ), + value: row[:"#{indicator.name}"] + } + end + + def value_wb_attributes(row, location, indicator) + { + location: location, + indicator: indicator, + sector: @sectors_index[row[:subsector]], + value: row[:responsetext] + } + end + + def submission_attributes(submission) + { + location: Location.find_by(iso_code3: submission[:iso]), + submission_type: submission[:type], + language: submission[:language], + submission_date: submission[:date_of_submission], + url: submission[:url] + } + end + + def import_categories_of(category_type) + @metadata. + map { |m| m[:"#{category_type.name}_category"] }. + select(&:itself). + uniq. + each do |name| + Indc::Category.create!(category_attributes(name, category_type)) + end + end + + def import_sources + @sources_index = @metadata. + map { |r| r[:source] }. + uniq. + each_with_object({}) do |source, memo| + memo[source] = Indc::Source.create!(name: source) + end + end + + def import_category_types + @category_types_index = @metadata.first.keys. + select { |key| key.match(/_category$/) }. + map { |c| c.to_s.gsub(/_category$/, '') }. + each_with_object({}) do |category_type, memo| + memo[category_type] = Indc::CategoryType.create!(name: category_type) + end + end + + def import_categories + Indc::CategoryType.all. + each do |category_type| + import_categories_of(category_type) + end + end + + def import_indicators + @metadata. + map { |r| [[r[:column_name], r[:source]], r] }. + uniq(&:first). + map(&:second). + each do |indicator, memo| + Indc::Indicator.create!(indicator_attributes(indicator)) + end + end + + def import_indicators_categories + @metadata.each do |r| + indicator = Indc::Indicator. + includes(:categories). + find_by!( + name: r[:column_name], + source: @sources_index[r[:source]] + ) + + categories = r.keys. + select { |key| key.match(/_category$/)}. + map { |c| c.to_s.gsub(/_category$/, '') }. + map do |category_type| + unless r[:"#{category_type}_category"].nil? + Indc::Category.find_by!( + name: r[:"#{category_type}_category"], + category_type: @category_types_index[category_type] + ) + end + end + + categories = categories. + select(&:itself). + reject do |category| + indicator.categories.include?(category) + end + + indicator.categories << categories + end + end + + def import_labels + indicators = @cait_labels. + group_by { |l| l[:indicator_name] }. + map { |k, v| [k, v.map { |i| i[:legend_item] }] }. + to_h + + indicators.each do |indicator_name, labels| + indicator = Indc::Indicator.find_by!(name: indicator_name) + labels.each_with_index do |label, index| + Indc::Label.create!({ + indicator: indicator, + value: label, + index: index + 1 + }) + end + end + end + + def import_values_cait + Indc::Indicator. + where(source: @sources_index['CAIT']). + each do |indicator| + @cait_data.each do |r| + location = @locations_by_iso3[r[:iso]] + unless location + Rails.logger.error "location #{d[:country]} not found. Skipping." + next + end + + Indc::Value.create!( + value_cait_attributes(r, location, indicator) + ) if r[:"#{indicator.name}"] + end + end + end + + def import_sectors + sectors = @wb_sectoral_data.map do |d| + d.slice(:sector, :subsector) + end + + @sectors_index = {} + sectors.uniq.each do |d| + parent = Indc::Sector.find_or_create_by( + name: d[:sector], + ) + sector = Indc::Sector.create!( + name: d[:subsector], + parent: parent + ) + + @sectors_index[d[:subsector]] = sector + end + end + + def import_values_wb + indicator_index = Indc::Indicator. + where(source: @sources_index['WB']). + group_by(&:name). + map { |k, v| [k, v.first] }. + to_h + + (@wb_wide_data + @wb_sectoral_data).each do |r| + location = @locations_by_iso2[r[:countrycode]] + unless location + Rails.logger.error "location #{r[:countrycode]} not found. Skipping." + next + end + + indicator = indicator_index[r[:questioncode]] + unless indicator + Rails.logger.error "indicator #{r[:questioncode]} not found. Skipping." + next + end + + Indc::Value.create!( + value_wb_attributes(r, location, indicator) + ) if r[:responsetext] + end + end + + def import_submissions + @submissions.each do |sub| + Indc::Submission.create!(submission_attributes(sub)) + end + end +end diff --git a/app/services/import_wb_indc.rb b/app/services/import_wb_indc.rb deleted file mode 100644 index 19526f970a..0000000000 --- a/app/services/import_wb_indc.rb +++ /dev/null @@ -1,159 +0,0 @@ -class ImportWbIndc - META_INDICATORS_FILEPATH = - "#{CW_FILES_PREFIX}wb_indc/CW_NDC_WB_metadata_w_definitions.csv".freeze - DATA_SECTORIAL_FILEPATH = - "#{CW_FILES_PREFIX}wb_indc/CW_NDC_WB_sectoral.csv".freeze - DATA_ECONOMY_WIDE_FILEPATH = - "#{CW_FILES_PREFIX}wb_indc/CW_NDC_WB_economy_wide.csv".freeze - - def call - cleanup - - load_csvs - - import_indicator_types - import_categories - import_indicators - import_sectors - import_values - - refresh_materialized_views - end - - private - - def load_csvs - @indicators = S3CSVReader.read(META_INDICATORS_FILEPATH).map(&:to_h) - @data_sectorial = S3CSVReader.read(DATA_SECTORIAL_FILEPATH).map(&:to_h) - @data_economy_wide = S3CSVReader.read(DATA_ECONOMY_WIDE_FILEPATH). - map(&:to_h) - @location_index = {} - @indicator_index = {} - @sector_index = {} - @ignored_indicators = [] - end - - def cleanup - WbIndc::Value.delete_all - WbIndc::Sector.delete_all - WbIndc::Category.delete_all - WbIndc::Indicator.delete_all - WbIndc::IndicatorType.delete_all - end - - def cached_location(countrycode) - if @location_index[countrycode] - @location_index[countrycode] - else - location = Location.where(iso_code2: countrycode).first - @location_index[countrycode] = location - location - end - end - - def indicator_attributes(i) - { - indicator_type: WbIndc::IndicatorType.find_by(name: i[:questiontype]), - code: i[:questioncode], - name: i[:questiontext], - description: i[:definition] - } - end - - def value_attributes(d, indicator, location) - unless location - Rails.logger.error "Location not found: #{d[:countrycode]}" and return - end - - unless indicator - unless @ignored_indicators.include?(d[:questioncode]) - Rails.logger.error "Indicator not found: #{d[:questioncode]}" - end or return - end - - { - indicator: indicator, - location: location, - sector: @sector_index[d[:subsector]], - value: d[:responsetext] - } - end - - def import_indicator_types - @indicators.map { |i| i[:questiontype] }.uniq.each do |t| - WbIndc::IndicatorType.create(name: t) - end - end - - def import_categories - indicators = @indicators.map do |i| - [i[:category], i[:category_2]] - end - - indicators = indicators.flatten.uniq.reject do |c| - c == 'NULL' - end - - indicators.each do |c| - WbIndc::Category.create( - name: c, - slug: Slug.create(c) - ) - end - end - - def import_indicators - @indicators.each do |i| - if i[:category_2] == 'NULL' - @ignored_indicators << i[:questioncode] - next - end - - indicator = WbIndc::Indicator.create( - indicator_attributes(i) - ) - - indicator.categories = WbIndc::Category.where( - name: [i[:category], i[:category_2]] - ) - - @indicator_index[i[:questioncode]] = indicator - end - end - - def import_sectors - sectors = @data_sectorial.map do |d| - d.slice(:sector, :subsector) - end - - sectors.uniq.each do |d| - parent = WbIndc::Sector.find_or_create_by(name: d[:sector]) - sector = WbIndc::Sector.create( - name: d[:subsector], - parent: parent - ) - - @sector_index[d[:subsector]] = sector - end - end - - def import_values - (@data_sectorial + @data_economy_wide).each do |d| - location = cached_location(d[:countrycode]) - indicator = @indicator_index[d[:questioncode]] - attributes = value_attributes(d, indicator, location) - next unless attributes - WbIndc::Value.create(attributes) - end - end - - def refresh_materialized_views - MaterializedView.refresh( - 'indc_categories', - 'indc_indicators', - 'indc_indicators_categories', - 'indc_sectors', - 'indc_values' - ) - end -end diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index cb8fe25c81..87b3daa8de 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -7,9 +7,8 @@ namespace :db do 'ndcs:full:index', 'sdgs:import', 'ndc_sdg_targets:import', + 'indc:import', 'historical_emissions:import', - 'cait_indc:import', - 'wb_indc:import', 'adaptation:import', 'wri_metadata:import', 'wb_extra:import', diff --git a/lib/tasks/indc.rake b/lib/tasks/indc.rake new file mode 100644 index 0000000000..1bcaeabcd2 --- /dev/null +++ b/lib/tasks/indc.rake @@ -0,0 +1,7 @@ +namespace :indc do + desc 'Imports the INDC dataset from the csv sources' + task import: :environment do + ImportIndc.new.call + end +end + diff --git a/spec/services/import_cait_indc_spec.rb b/spec/services/import_cait_indc_spec.rb deleted file mode 100644 index 0fc0e951d6..0000000000 --- a/spec/services/import_cait_indc_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'rails_helper' - -object_contents = { - "#{CW_FILES_PREFIX}cait_indc/CW_NDC_CAIT_metadata.csv" => <<~END, - category,column_name,long_name - General,domestic_approval,Domestic Approval Processes Category - END - "#{CW_FILES_PREFIX}cait_indc/CW_NDC_CAIT_legend.csv" => <<~END, - indicator_name,legend_item,chart_title - domestic_approval,Executive,Domestic Approval Process - domestic_approval,Executive + notify legislature,Domestic Approval Process - domestic_approval,Executive + majority of one legislative body,Domestic Approval Process - domestic_approval,Executive + majority of two legislative bodies/super-majority of one legislative body,Domestic Approval Process - domestic_approval,Multiple executive and legislative bodies,Domestic Approval Process - domestic_approval,Not yet included in analysis,Domestic Approval Process - END - "#{CW_FILES_PREFIX}cait_indc/CW_NDC_CAIT_data.csv" => <<~END, - country,ISO,domestic_approval,domestic_approval_label - Afghanistan,AFG,Executive + majority of two legislative bodies,Executive + majority of two legislative bodies/super-majority of one legislative body - Algeria,DZA,Executive,Executive - Andorra,AND,Not yet included in analysis - END - "#{CW_FILES_PREFIX}cait_indc/CW_NDC_Submission_URL.csv" => <<~END - ISO,Country,Type,Language,Date_of_Submission,URL - AFG,Afghanistan,INDC,English,10/13/2015,http://www4.unfccc.int/Submissions/INDC/Published Documents/Afghanistan/1/INDC_AFG_Paper_En_20150927_.docx FINAL.pdf - END -} - -RSpec.describe ImportCaitIndc do - subject { ImportCaitIndc.new.call } - - before :all do - Aws.config[:s3] = { - stub_responses: { - get_object: lambda { |context| - {body: object_contents[context.params[:key]]} - } - } - } - end - - before :each do - [{ - iso_code3: 'AFG', - wri_standard_name: 'Afghanistan' - }, { - iso_code3: 'DZA', - wri_standard_name: 'Algeria' - }, { - iso_code3: 'AND', - wri_standard_name: 'Andorra' - }].each do |c| - FactoryGirl.create(:location, c) - end - end - - after :all do - Aws.config[:s3] = { - stub_responses: nil - } - end - - it 'Creates new CAIT INDC records' do - expect { subject }.to change { CaitIndc::Value.count }.by(3) - end - - it 'Creates new INDC submission records' do - expect { subject }.to change { CaitIndc::Submission.count }.by(1) - end -end diff --git a/spec/services/import_global_indc_spec.rb b/spec/services/import_global_indc_spec.rb deleted file mode 100644 index 8f45dd74d7..0000000000 --- a/spec/services/import_global_indc_spec.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'rails_helper' - -object_contents = { - "#{CW_FILES_PREFIX}cait_indc/CW_NDC_CAIT_metadata.csv" => <<~END, - category,column_name,long_name - General,domestic_approval,Domestic Approval Processes Category - END - "#{CW_FILES_PREFIX}cait_indc/CW_NDC_CAIT_legend.csv" => <<~END, - indicator_name,legend_item,chart_title - domestic_approval,Executive,Domestic Approval Process - domestic_approval,Executive + notify legislature,Domestic Approval Process - domestic_approval,Executive + majority of one legislative body,Domestic Approval Process - domestic_approval,Executive + majority of two legislative bodies/super-majority of one legislative body,Domestic Approval Process - domestic_approval,Multiple executive and legislative bodies,Domestic Approval Process - domestic_approval,Not yet included in analysis,Domestic Approval Process - END - "#{CW_FILES_PREFIX}cait_indc/CW_NDC_CAIT_data.csv" => <<~END, - country,ISO,domestic_approval,domestic_approval_label - Afghanistan,AFG,Executive + majority of two legislative bodies,Executive + majority of two legislative bodies/super-majority of one legislative body - Algeria,DZA,Executive,Executive - Andorra,AND,Not yet included in analysis - END - "#{CW_FILES_PREFIX}cait_indc/CW_NDC_Submission_URL.csv" => <<~END, - ISO,Country,Type,Language,Date_of_Submission,URL - AFG,Afghanistan,INDC,English,10/13/2015,http://www4.unfccc.int/Submissions/INDC/Published Documents/Afghanistan/1/INDC_AFG_Paper_En_20150927_.docx FINAL.pdf - END - "#{CW_FILES_PREFIX}wb_indc/CW_NDC_WB_metadata_w_definitions.csv" => <<~END, - QuestionType,category,category_2,QuestionCode,QuestionText,Definition - Adaptation,General Information,Adaptation Target,A_Tg_AdInclu,Adaptation Included in INDC (Yes/No),Whether or not the NDC includes adaptation - Adaptation,General Information,Adaptation Target,A_Tg_TarYr,Target Year for Adaptation,The year by which adaptation objectives are expected to be achieved - Adaptation,Sectoral Information,Sectoral Commitments,A_Sc_CapBud,Capacity Building Needs for Sectorial Implementation - Adaptation,Sectoral Information,Sectoral Commitments,A_Sc_ConAct,Sectorial Conditional Actions - Mitigation,Economy-wide Information,Target,M_TarYr,Target year - END - "#{CW_FILES_PREFIX}wb_indc/CW_NDC_WB_sectoral.csv" => <<~END, - CountryCode,Sector,SubSector,QuestionCode,ResponseText - AF,Water,Water Infrastructure,A_Sc_CapBud,Ecological engineering and spatial planning for water resources - AF,Water,Water Infrastructure,A_Sc_ConAct,"Development of water resources through rehabilitation and reconstruction of small-, medium-, and large-scale infrastructure" - END - "#{CW_FILES_PREFIX}wb_indc/CW_NDC_WB_economy_wide.csv" => <<~END, - CountryCode,QuestionCode,ResponseText - AF,A_Tg_AdInclu,Yes - AF,A_Tg_TarYr,2030 - AF,M_TarYr,2030 - DZ,A_Tg_AdInclu,Yes - DZ,A_Tg_TarYr,2030 - DZ,M_TarYr,2030 - END - "#{CW_FILES_PREFIX}global_indc/CW_NDC_metadata_combined.csv" => <<~END - category,category_2,column_name,long_name,Definition,Source - Overview,NDC,domestic_approval,,,CAIT - Mitigation,Target,A_Tg_AdInclu,,,WB - Mitigation,Target,M_TarYr,,,WB - END -} - -RSpec.describe ImportGlobalIndc do - subject { ImportGlobalIndc.new.call } - - before :all do - Aws.config[:s3] = { - stub_responses: { - get_object: lambda { |context| - {body: object_contents[context.params[:key]]} - } - } - } - end - - before :each do - [{ - iso_code3: 'AFG', - iso_code2: 'AF', - wri_standard_name: 'Afghanistan' - }, { - iso_code3: 'DZA', - iso_code2: 'DZ', - wri_standard_name: 'Algeria' - }, { - iso_code3: 'AND', - iso_code2: 'AD', - wri_standard_name: 'Andorra' - }].each do |c| - FactoryGirl.create(:location, c) - end - - ImportCaitIndc.new.call - ImportWbIndc.new.call - end - - after :all do - Aws.config[:s3] = { - stub_responses: nil - } - end - - it 'Creates new global INDC categories' do - expect { subject }.to change { GlobalIndc::Category.count }.by(4) - end - - it 'Creates new global INDC indicator links' do - expect { subject }.to change { GlobalIndc::Indicator.count }.by(3) - end -end diff --git a/spec/services/import_indc.rb b/spec/services/import_indc.rb new file mode 100644 index 0000000000..db191002ca --- /dev/null +++ b/spec/services/import_indc.rb @@ -0,0 +1,116 @@ +require 'rails_helper' + +object_contents = { + "#{CW_FILES_PREFIX}indc/NDC_metadata.csv" => <<~END, + global_category,main_category,map_category,column_name,long_name,Definition,Source + Overview,UNFCCC Process,Other,domestic_approval,Domestic Approval Processes Category,,CAIT + Mitigation,Target,,M_TarYr,Target year,The year by which mitigation objectives are expected to be achieved,WB + Mitigation,Target,,M_TarYr_2,Second target year,Whether the NDC has a second-year target,WB + Adaptation,Adaptation Target,,A_Tg_AdInclu,Adaptation Included in INDC (Yes/No),Whether or not the NDC includes adaptation,WB + Adaptation,Adaptation Target,,A_Tg_TarYr,Target Year for Adaptation,The year by which adaptation objectives are expected to be achieved,WB + Sectoral Information,Sectoral Adaptation Commitments,,A_Sc_ConAct,Sectorial Conditional Actions,Condition actions of the sectoral level,WB + Sectoral Information,Sectoral Adaptation Commitments,,A_Sc_ConActP,Page Number for Sectorial Conditional Actions,Link to the page reference for the sectoral condition actions,WB + Sectoral Information,Sectoral Adaptation Commitments,,A_Sc_ConActImp,Implementing Agency for Sectorial Conditonal Actions,The agency responsible for implementing the sectoral conditional action,WB + Sectoral Information,Sectoral Adaptation Commitments,,A_Sc_ConActDon,Funders for Sectorial Conditional Actions,The funders for sectoral conditional actions,WB + Sectoral Information,Sectoral Adaptation Commitments,,A_Sc_ConActCost,Estimated Cost for Sectorial Conditional Actions ,The estimated costs for sectoral conditional actions,WB + Sectoral Information,,,A_Sc_ConActCostH,Estimated Cost for Sectorial Conditional Actions (Harmonized in Million USD),,WB + Sectoral Information,Sectoral Adaptation Commitments,,A_Sc_CapBud,Capacity Building Needs for Sectorial Implementation,Capacity building needs for sectorial implementation,WB + END + + "#{CW_FILES_PREFIX}indc/NDC_submission.csv" => <<~END, + ISO,Country,Type,Language,Date_of_Submission,URL + AFG,Afghanistan,INDC,English,10/13/2015,http://www4.unfccc.int/Submissions/INDC/Published Documents/Afghanistan/1/INDC_AFG_Paper_En_20150927_.docx FINAL.pdf + END + + "#{CW_FILES_PREFIX}indc/NDC_CAIT_data.csv" => <<~END, + country,ISO,domestic_approval,domestic_approval_label + Afghanistan,AFG,Executive + majority of two legislative bodies,Executive + majority of two legislative bodies/super-majority of one legislative body + Algeria,DZA,Executive,Executive + Andorra,AND,Not yet included in analysis + END + + "#{CW_FILES_PREFIX}indc/NDC_CAIT_legend.csv" => <<~END, + indicator_name,legend_item,chart_title + domestic_approval,Executive,Domestic Approval Process + domestic_approval,Executive + notify legislature,Domestic Approval Process + domestic_approval,Executive + majority of one legislative body,Domestic Approval Process + domestic_approval,Executive + majority of two legislative bodies/super-majority of one legislative body,Domestic Approval Process + domestic_approval,Multiple executive and legislative bodies,Domestic Approval Process + domestic_approval,Not yet included in analysis,Domestic Approval Process + END + + "#{CW_FILES_PREFIX}indc/NDC_WB_data_sectoral.csv" => <<~END, + CountryCode,Sector,SubSector,QuestionCode,ResponseText + AF,Water,Water Infrastructure,A_Sc_CapBud,Ecological engineering and spatial planning for water resources + AF,Water,Water Infrastructure,A_Sc_ConAct,"Development of water resources through rehabilitation and reconstruction of small-, medium-, and large-scale infrastructure" + END + + "#{CW_FILES_PREFIX}indc/NDC_WB_data_wide.csv" => <<~END, + CountryCode,QuestionCode,ResponseText + AF,A_Tg_AdInclu,Yes + AF,A_Tg_TarYr,2030 + AF,M_TarYr,2030 + DZ,A_Tg_AdInclu,Yes + DZ,A_Tg_TarYr,2030 + DZ,M_TarYr,2030 + END + +} + +describe ImportIndc do + subject { ImportIndc.new.call } + + before :all do + Aws.config[:s3] = { + stub_responses: { + get_object: lambda { |context| + {body: object_contents[context.params[:key]]} + } + } + } + end + + before :each do + [{ + iso_code3: 'AFG', + iso_code2: 'AF', + wri_standard_name: 'Afghanistan' + }, { + iso_code3: 'DZA', + iso_code2: 'DZ', + wri_standard_name: 'Algeria' + }, { + iso_code3: 'AND', + iso_code2: 'AD', + wri_standard_name: 'Andorra' + }].each do |c| + FactoryGirl.create(:location, c) + end + end + + after :all do + Aws.config[:s3] = { + stub_responses: nil + } + end + + it 'Creates new INDC source records' do + expect { subject }.to change { Indc::Source.count }.by(2) + end + + it 'Creates new INDC indicator records' do + expect { subject }.to change { Indc::Indicator.count }.by(12) + end + + it 'Creates new INDC sector records' do + expect { subject }.to change { Indc::Sector.count }.by(2) + end + + it 'Creates new INDC value records' do + expect { subject }.to change { Indc::Value.count }.by(11) + end + + it 'Creates new INDC submission records' do + expect { subject }.to change { Indc::Submission.count }.by(1) + end +end diff --git a/spec/services/import_wb_indc_spec.rb b/spec/services/import_wb_indc_spec.rb deleted file mode 100644 index c4efb9e0d9..0000000000 --- a/spec/services/import_wb_indc_spec.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'rails_helper' - -object_contents = { - "#{CW_FILES_PREFIX}wb_indc/CW_NDC_WB_metadata_w_definitions.csv" => <<~END, - QuestionType,category,category_2,QuestionCode,QuestionText,Definition - Adaptation,General Information,Adaptation Target,A_Tg_AdInclu,Adaptation Included in INDC (Yes/No),Whether or not the NDC includes adaptation - Adaptation,General Information,Adaptation Target,A_Tg_TarYr,Target Year for Adaptation,The year by which adaptation objectives are expected to be achieved - Adaptation,Sectoral Information,Sectoral Commitments,A_Sc_CapBud,Capacity Building Needs for Sectorial Implementation - Adaptation,Sectoral Information,Sectoral Commitments,A_Sc_ConAct,Sectorial Conditional Actions - Mitigation,Economy-wide Information,Target,M_TarYr,Target year - END - "#{CW_FILES_PREFIX}wb_indc/CW_NDC_WB_sectoral.csv" => <<~END, - CountryCode,Sector,SubSector,QuestionCode,ResponseText - AF,Water,Water Infrastructure,A_Sc_CapBud,Ecological engineering and spatial planning for water resources - AF,Water,Water Infrastructure,A_Sc_ConAct,"Development of water resources through rehabilitation and reconstruction of small-, medium-, and large-scale infrastructure" - END - "#{CW_FILES_PREFIX}wb_indc/CW_NDC_WB_economy_wide.csv" => <<~END - CountryCode,QuestionCode,ResponseText - AF,A_Tg_AdInclu,Yes - AF,A_Tg_TarYr,2030 - AF,M_TarYr,2030 - DZ,A_Tg_AdInclu,Yes - DZ,A_Tg_TarYr,2030 - DZ,M_TarYr,2030 - END -} - -describe ImportWbIndc do - subject { ImportWbIndc.new.call } - - before :all do - Aws.config[:s3] = { - stub_responses: { - get_object: lambda { |context| - {body: object_contents[context.params[:key]]} - } - } - } - end - - before :each do - [{ - iso_code2: 'AF', - wri_standard_name: 'Afghanistan' - }, { - iso_code2: 'DZ', - wri_standard_name: 'Algeria' - }].each do |c| - FactoryGirl.create(:location, c) - end - end - - after :all do - Aws.config[:s3] = { - stub_responses: nil - } - end - - it 'Creates new WB INDC records' do - expect { subject }.to change { WbIndc::Value.count }.by(8) - end -end From ef1fc16ffad2e76e260412c0177d5b08936f5384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Mon, 30 Oct 2017 20:37:23 +0000 Subject: [PATCH 26/43] Update indc endpoint and serializers --- app/controllers/api/v1/ndcs_controller.rb | 35 +++++++------------ .../api/v1/indc/category_serializer.rb | 5 ++- .../api/v1/indc/indicator_serializer.rb | 4 +++ .../api/v1/indc/label_serializer.rb | 4 +-- .../api/v1/ndcs_controller_spec.rb | 19 ++++------ spec/factories/indc_indicators.rb | 9 ++++- 6 files changed, 37 insertions(+), 39 deletions(-) diff --git a/app/controllers/api/v1/ndcs_controller.rb b/app/controllers/api/v1/ndcs_controller.rb index d4f400696b..185797b6dd 100644 --- a/app/controllers/api/v1/ndcs_controller.rb +++ b/app/controllers/api/v1/ndcs_controller.rb @@ -9,7 +9,7 @@ module V1 'coverage_sectors', 'coverage_sectors_short', 'other_adaption info' - ].freeze + ].map { |s| "cait_#{s}"}.freeze NdcIndicators = Struct.new(:indicators, :categories, :sectors) do alias_method :read_attribute_for_serialization, :send @@ -21,12 +21,16 @@ module V1 class NdcsController < ApiController def index - categories = ::Indc::Category.all + categories = ::Indc::Category. + includes(:category_type). + all sectors = ::Indc::Sector.all if params[:filter] categories = categories.where( - category_type: params[:filter] + indc_category_types: { + name: params[:filter] + } ) end @@ -65,7 +69,8 @@ def content_overview def indicators indicators = ::Indc::Indicator.includes( :labels, - :categories, + :source, + categories: [:category_type], values: [:location] ) @@ -77,30 +82,14 @@ def indicators if params[:filter] indicators = indicators.where( - indc_categories: {category_type: params[:filter]} + indc_category_types: { + name: params[:filter] + } ) end if params[:category] - indicator_ids = ::GlobalIndc::Category. - includes(:indicators, children: :indicators). - where( - parent_id: nil, - slug: params[:category] - ). - flat_map(&:children). - flat_map(&:indicators). - map do |indicator| - if indicator.wb_indicator_id - "wb#{indicator.wb_indicator_id}" - elsif indicator.cait_indicator_id - "cait#{indicator.cait_indicator_id}" - end - end - indicators = indicators.where( - id: indicator_ids - ) end indicators diff --git a/app/serializers/api/v1/indc/category_serializer.rb b/app/serializers/api/v1/indc/category_serializer.rb index 665e5bdb51..a33d7649ea 100644 --- a/app/serializers/api/v1/indc/category_serializer.rb +++ b/app/serializers/api/v1/indc/category_serializer.rb @@ -3,9 +3,12 @@ module V1 module Indc class CategorySerializer < ActiveModel::Serializer attribute :name - attribute :source attribute :slug attribute :category_type, key: :type + + def category_type + object.category_type.name + end end end end diff --git a/app/serializers/api/v1/indc/indicator_serializer.rb b/app/serializers/api/v1/indc/indicator_serializer.rb index bc43d071af..571614871d 100644 --- a/app/serializers/api/v1/indc/indicator_serializer.rb +++ b/app/serializers/api/v1/indc/indicator_serializer.rb @@ -11,6 +11,10 @@ class IndicatorSerializer < ActiveModel::Serializer attribute :labels attribute :locations + def source + object.source.name + end + def labels IndexedSerializer.serialize( object.labels, diff --git a/app/serializers/api/v1/indc/label_serializer.rb b/app/serializers/api/v1/indc/label_serializer.rb index 58733ab700..3f1c152423 100644 --- a/app/serializers/api/v1/indc/label_serializer.rb +++ b/app/serializers/api/v1/indc/label_serializer.rb @@ -2,8 +2,8 @@ module Api module V1 module Indc class LabelSerializer < ActiveModel::Serializer - attributes :name - attributes :index + attribute :value, key: :name + attribute :index end end end diff --git a/spec/controllers/api/v1/ndcs_controller_spec.rb b/spec/controllers/api/v1/ndcs_controller_spec.rb index 31dca0ad59..64739dfd55 100644 --- a/spec/controllers/api/v1/ndcs_controller_spec.rb +++ b/spec/controllers/api/v1/ndcs_controller_spec.rb @@ -5,17 +5,12 @@ let(:parsed_body) { JSON.parse(response.body) } - let!(:some_cait_indc_values) { - list = FactoryGirl.create_list(:cait_indc_indicator_with_dependants, 3) - MaterializedView.refresh( - 'indc_categories', - 'indc_indicators', - 'indc_indicators_categories', - 'indc_labels', - 'indc_sectors', - 'indc_values' + let!(:some_indc_values) { + FactoryGirl.create_list( + :indc_indicator, + 3, + :with_dependants ) - list } describe 'GET index' do @@ -24,7 +19,7 @@ expect(response).to be_success end - it 'lists all cait indc indicators' do + it 'lists all indc indicators' do get :index expect(parsed_body['indicators'].length).to eq(3) end @@ -32,7 +27,7 @@ describe 'GET content_overview' do it 'returns a successful 200 response' do - code = some_cait_indc_values.first.values.first.location.iso_code3 + code = some_indc_values.first.values.first.location.iso_code3 get :content_overview, params: {code: code} expect(response).to be_success end diff --git a/spec/factories/indc_indicators.rb b/spec/factories/indc_indicators.rb index f95ce71958..95cd78c65a 100644 --- a/spec/factories/indc_indicators.rb +++ b/spec/factories/indc_indicators.rb @@ -8,6 +8,7 @@ transient do values_count 3 labels_count 2 + sectors_count 2 categories_count 2 end @@ -18,6 +19,11 @@ indicator: indicator ) + sector = create( + :indc_sector, + parent: create(:indc_sector) + ) + indicator.categories = create_list( :indc_category, evaluator.categories_count @@ -27,7 +33,8 @@ :indc_value, evaluator.values_count, indicator: indicator, - label: labels.sample + label: labels.sample, + sector: sector ) end end From ed1eb1c0b93344ce14917dc015beb7241c05d1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Tue, 31 Oct 2017 14:53:58 +0000 Subject: [PATCH 27/43] Make rubocop happy --- app/controllers/api/v1/ndcs_controller.rb | 2 +- app/services/import_indc.rb | 73 +++++++++++++---------- lib/tasks/indc.rake | 1 - spec/factories/indc_categories.rb | 2 +- spec/factories/indc_category_types.rb | 3 +- spec/factories/indc_indicators.rb | 2 +- spec/factories/indc_labels.rb | 2 +- spec/factories/indc_sources.rb | 2 +- spec/models/indc/submission_spec.rb | 2 +- 9 files changed, 47 insertions(+), 42 deletions(-) diff --git a/app/controllers/api/v1/ndcs_controller.rb b/app/controllers/api/v1/ndcs_controller.rb index 185797b6dd..27c861f989 100644 --- a/app/controllers/api/v1/ndcs_controller.rb +++ b/app/controllers/api/v1/ndcs_controller.rb @@ -9,7 +9,7 @@ module V1 'coverage_sectors', 'coverage_sectors_short', 'other_adaption info' - ].map { |s| "cait_#{s}"}.freeze + ].map { |s| "cait_#{s}" }.freeze NdcIndicators = Struct.new(:indicators, :categories, :sectors) do alias_method :read_attribute_for_serialization, :send diff --git a/app/services/import_indc.rb b/app/services/import_indc.rb index e3b5b75c0a..9f4db328ce 100644 --- a/app/services/import_indc.rb +++ b/app/services/import_indc.rb @@ -1,11 +1,16 @@ class ImportIndc - DATA_CAIT_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_CAIT_data.csv". - freeze - LEGEND_CAIT_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_CAIT_legend.csv".freeze - DATA_WB_WIDE_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_WB_data_wide.csv".freeze - DATA_WB_SECTORAL_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_WB_data_sectoral.csv".freeze - SUBMISSIONS_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_submission.csv".freeze - METADATA_FILEPATH = "#{CW_FILES_PREFIX}indc/NDC_metadata.csv".freeze + DATA_CAIT_FILEPATH = + "#{CW_FILES_PREFIX}indc/NDC_CAIT_data.csv".freeze + LEGEND_CAIT_FILEPATH = + "#{CW_FILES_PREFIX}indc/NDC_CAIT_legend.csv".freeze + DATA_WB_WIDE_FILEPATH = + "#{CW_FILES_PREFIX}indc/NDC_WB_data_wide.csv".freeze + DATA_WB_SECTORAL_FILEPATH = + "#{CW_FILES_PREFIX}indc/NDC_WB_data_sectoral.csv".freeze + SUBMISSIONS_FILEPATH = + "#{CW_FILES_PREFIX}indc/NDC_submission.csv".freeze + METADATA_FILEPATH = + "#{CW_FILES_PREFIX}indc/NDC_metadata.csv".freeze def call cleanup @@ -138,7 +143,7 @@ def import_category_types def import_categories Indc::CategoryType.all. each do |category_type| - import_categories_of(category_type) + import_categories_of(category_type) end end @@ -147,7 +152,7 @@ def import_indicators map { |r| [[r[:column_name], r[:source]], r] }. uniq(&:first). map(&:second). - each do |indicator, memo| + each do |indicator| Indc::Indicator.create!(indicator_attributes(indicator)) end end @@ -162,15 +167,15 @@ def import_indicators_categories ) categories = r.keys. - select { |key| key.match(/_category$/)}. + select { |key| key.match(/_category$/) }. map { |c| c.to_s.gsub(/_category$/, '') }. map do |category_type| - unless r[:"#{category_type}_category"].nil? - Indc::Category.find_by!( - name: r[:"#{category_type}_category"], - category_type: @category_types_index[category_type] - ) - end + next if r[:"#{category_type}_category"].nil? + + Indc::Category.find_by!( + name: r[:"#{category_type}_category"], + category_type: @category_types_index[category_type] + ) end categories = categories. @@ -192,30 +197,30 @@ def import_labels indicators.each do |indicator_name, labels| indicator = Indc::Indicator.find_by!(name: indicator_name) labels.each_with_index do |label, index| - Indc::Label.create!({ + Indc::Label.create!( indicator: indicator, value: label, index: index + 1 - }) + ) end end end def import_values_cait - Indc::Indicator. - where(source: @sources_index['CAIT']). - each do |indicator| - @cait_data.each do |r| - location = @locations_by_iso3[r[:iso]] - unless location - Rails.logger.error "location #{d[:country]} not found. Skipping." - next - end - - Indc::Value.create!( - value_cait_attributes(r, location, indicator) - ) if r[:"#{indicator.name}"] + Indc::Indicator.where(source: @sources_index['CAIT']).each do |indicator| + @cait_data.each do |r| + location = @locations_by_iso3[r[:iso]] + unless location + Rails.logger.error "location #{d[:country]} not found. Skipping." + next end + + next unless r[:"#{indicator.name}"] + + Indc::Value.create!( + value_cait_attributes(r, location, indicator) + ) + end end end @@ -227,7 +232,7 @@ def import_sectors @sectors_index = {} sectors.uniq.each do |d| parent = Indc::Sector.find_or_create_by( - name: d[:sector], + name: d[:sector] ) sector = Indc::Sector.create!( name: d[:subsector], @@ -258,9 +263,11 @@ def import_values_wb next end + next unless r[:responsetext] + Indc::Value.create!( value_wb_attributes(r, location, indicator) - ) if r[:responsetext] + ) end end diff --git a/lib/tasks/indc.rake b/lib/tasks/indc.rake index 1bcaeabcd2..1772f98933 100644 --- a/lib/tasks/indc.rake +++ b/lib/tasks/indc.rake @@ -4,4 +4,3 @@ namespace :indc do ImportIndc.new.call end end - diff --git a/spec/factories/indc_categories.rb b/spec/factories/indc_categories.rb index 949d633b7c..3b2d5c4848 100644 --- a/spec/factories/indc_categories.rb +++ b/spec/factories/indc_categories.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :indc_category, class: 'Indc::Category' do name 'MyName' - sequence :slug { |n| "my-slug-" + ('AA'..'ZZ').to_a[n] } + sequence :slug { |n| 'my-slug-' + ('AA'..'ZZ').to_a[n] } association :category_type, factory: :indc_category_type end end diff --git a/spec/factories/indc_category_types.rb b/spec/factories/indc_category_types.rb index bc544db547..23dba56bb2 100644 --- a/spec/factories/indc_category_types.rb +++ b/spec/factories/indc_category_types.rb @@ -1,6 +1,5 @@ FactoryGirl.define do factory :indc_category_type, class: 'Indc::CategoryType' do - sequence :name { |n| "My Name " + ('AA'..'ZZ').to_a[n] } + sequence :name { |n| 'My Name ' + ('AA'..'ZZ').to_a[n] } end end - diff --git a/spec/factories/indc_indicators.rb b/spec/factories/indc_indicators.rb index 95cd78c65a..f66b90dcd6 100644 --- a/spec/factories/indc_indicators.rb +++ b/spec/factories/indc_indicators.rb @@ -2,7 +2,7 @@ factory :indc_indicator, class: 'Indc::Indicator' do name 'MyName' association :source, factory: :indc_source - sequence :slug { |n| "my-slug-" + ('AA'..'ZZ').to_a[n] } + sequence :slug { |n| 'my-slug-' + ('AA'..'ZZ').to_a[n] } trait :with_dependants do transient do diff --git a/spec/factories/indc_labels.rb b/spec/factories/indc_labels.rb index d4051bdd96..18c8cdab9e 100644 --- a/spec/factories/indc_labels.rb +++ b/spec/factories/indc_labels.rb @@ -2,6 +2,6 @@ factory :indc_label, class: 'Indc::Label' do association :indicator, factory: :indc_indicator value 'MyLabel' - sequence :index { |n| (0..99).to_a[n] } + sequence :index { |n| (0..99).to_a[n] } end end diff --git a/spec/factories/indc_sources.rb b/spec/factories/indc_sources.rb index bfcaeffe41..8e4d029050 100644 --- a/spec/factories/indc_sources.rb +++ b/spec/factories/indc_sources.rb @@ -1,5 +1,5 @@ FactoryGirl.define do factory :indc_source, class: 'Indc::Source' do - sequence :name { |n| "My Name " + ('AA'..'ZZ').to_a[n] } + sequence :name { |n| 'My Name ' + ('AA'..'ZZ').to_a[n] } end end diff --git a/spec/models/indc/submission_spec.rb b/spec/models/indc/submission_spec.rb index 5211078405..80642cbbeb 100644 --- a/spec/models/indc/submission_spec.rb +++ b/spec/models/indc/submission_spec.rb @@ -25,7 +25,7 @@ ).to have(2).errors_on(:url) end - it 'should be invalid when url present but invalid' do + it 'should be invalid when url present but invalid' do expect( FactoryGirl.build(:indc_submission, url: 'not an url') ).to have(1).errors_on(:url) From 15c1f4248c76a596ba4ecf031943b0f4e612608e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Tue, 31 Oct 2017 15:00:07 +0000 Subject: [PATCH 28/43] Add test case for category import --- spec/services/import_indc.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/services/import_indc.rb b/spec/services/import_indc.rb index db191002ca..d51894d125 100644 --- a/spec/services/import_indc.rb +++ b/spec/services/import_indc.rb @@ -98,6 +98,10 @@ expect { subject }.to change { Indc::Source.count }.by(2) end + it 'Creates new INDC category records' do + expect { subject }.to change { Indc::Category.count }.by(9) + end + it 'Creates new INDC indicator records' do expect { subject }.to change { Indc::Indicator.count }.by(12) end From 86e35bf5d7f246883193c045489ec67ffb21c726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Tue, 31 Oct 2017 15:10:48 +0000 Subject: [PATCH 29/43] Add up to date schema.rb --- db/schema.rb | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index aced25991b..b98ba30b45 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171027170320) do +ActiveRecord::Schema.define(version: 20171031100456) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -264,15 +264,32 @@ create_table "quantification_values", force: :cascade do |t| t.bigint "location_id" t.bigint "label_id" - t.integer "year" - t.float "value" - t.boolean "range" + t.integer "year", limit: 2 + t.float "first_value" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.float "second_value" t.index ["label_id"], name: "index_quantification_values_on_label_id" t.index ["location_id"], name: "index_quantification_values_on_location_id" end + create_table "socioeconomic_indicators", force: :cascade do |t| + t.bigint "location_id" + t.integer "year", limit: 2, null: false + t.bigint "gdp" + t.integer "gdp_rank", limit: 2 + t.float "gdp_per_capita" + t.integer "gdp_per_capita_rank" + t.bigint "population" + t.integer "population_rank", limit: 2 + t.float "population_growth" + t.integer "population_growth_rank", limit: 2 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["location_id", "year"], name: "index_socioeconomic_indicators_on_location_id_and_year", unique: true + t.index ["location_id"], name: "index_socioeconomic_indicators_on_location_id" + end + create_table "timeline_documents", force: :cascade do |t| t.bigint "source_id" t.bigint "location_id" @@ -371,6 +388,7 @@ add_foreign_key "ndcs", "locations", on_delete: :cascade add_foreign_key "quantification_values", "locations", on_delete: :cascade add_foreign_key "quantification_values", "quantification_labels", column: "label_id", on_delete: :cascade + add_foreign_key "socioeconomic_indicators", "locations", on_delete: :cascade add_foreign_key "timeline_documents", "locations", on_delete: :cascade add_foreign_key "timeline_documents", "timeline_sources", column: "source_id", on_delete: :cascade add_foreign_key "timeline_notes", "timeline_documents", column: "document_id", on_delete: :cascade From f72cc130fdb5c82366833e18be19abe82c470716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Tue, 31 Oct 2017 15:12:04 +0000 Subject: [PATCH 30/43] Update main db import task --- lib/tasks/db.rake | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 87b3daa8de..0b1af3d592 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -13,7 +13,6 @@ namespace :db do 'wri_metadata:import', 'wb_extra:import', 'timeline:import', - 'global_indc:import', 'quantifications:import', 'socioeconomics:import' ] From b82a0b06ed8bc1a7d9407a1573a22d1d77f5c84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Tue, 31 Oct 2017 17:39:23 +0000 Subject: [PATCH 31/43] Change indicator slug creation --- app/controllers/api/v1/ndcs_controller.rb | 2 +- app/services/import_indc.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/ndcs_controller.rb b/app/controllers/api/v1/ndcs_controller.rb index 27c861f989..9060afaac4 100644 --- a/app/controllers/api/v1/ndcs_controller.rb +++ b/app/controllers/api/v1/ndcs_controller.rb @@ -9,7 +9,7 @@ module V1 'coverage_sectors', 'coverage_sectors_short', 'other_adaption info' - ].map { |s| "cait_#{s}" }.freeze + ].freeze NdcIndicators = Struct.new(:indicators, :categories, :sectors) do alias_method :read_attribute_for_serialization, :send diff --git a/app/services/import_indc.rb b/app/services/import_indc.rb index 9f4db328ce..e27d978215 100644 --- a/app/services/import_indc.rb +++ b/app/services/import_indc.rb @@ -75,7 +75,7 @@ def category_attributes(name, category_type) def indicator_attributes(indicator) { name: indicator[:column_name], - slug: Slug.create("#{indicator[:source]}_#{indicator[:column_name]}"), + slug: Slug.create(indicator[:column_name]), description: indicator[:long_name], source: @sources_index[indicator[:source]] } From 41c105abd54b869b905514e95cd52fd964db76d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Tue, 31 Oct 2017 17:49:02 +0000 Subject: [PATCH 32/43] Add integer conversion To check for ids in string format inside lists --- .../app/components/ndcs-map/ndcs-map-selectors.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/app/components/ndcs-map/ndcs-map-selectors.js b/app/javascript/app/components/ndcs-map/ndcs-map-selectors.js index 1e5c956655..e866d9a43b 100644 --- a/app/javascript/app/components/ndcs-map/ndcs-map-selectors.js +++ b/app/javascript/app/components/ndcs-map/ndcs-map-selectors.js @@ -47,7 +47,8 @@ export const getSelectedCategory = createSelector( } const firstCategorywithIndicators = categories.find(category => indicators.some( - indicator => indicator.category_ids.indexOf(category.id) > -1 + indicator => + indicator.category_ids.indexOf(parseInt(category.id, 10)) > -1 ) ); return firstCategorywithIndicators || categories[0]; @@ -59,9 +60,8 @@ export const getSelectedCategory = createSelector( export const getCategoryIndicators = createSelector( [getIndicatorsParsed, getSelectedCategory], (indicatorsParsed, category) => { - const categoryId = category.id; const categoryIndicators = indicatorsParsed.filter( - indicator => indicator.categoryIds.indexOf(categoryId) > -1 + indicator => indicator.categoryIds.indexOf(parseInt(category.id, 10)) > -1 ); return categoryIndicators; } From 9217a177c68efb57fdb505cefd89859d4466ab5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Tue, 31 Oct 2017 17:49:20 +0000 Subject: [PATCH 33/43] Fix import process --- app/services/import_indc.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/import_indc.rb b/app/services/import_indc.rb index e27d978215..1137855775 100644 --- a/app/services/import_indc.rb +++ b/app/services/import_indc.rb @@ -211,7 +211,7 @@ def import_values_cait @cait_data.each do |r| location = @locations_by_iso3[r[:iso]] unless location - Rails.logger.error "location #{d[:country]} not found. Skipping." + Rails.logger.error "location #{r[:country]} not found. Skipping." next end From 672e4d7b0fe18625352dae80b32af50fc4dbc95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Tue, 31 Oct 2017 18:09:56 +0000 Subject: [PATCH 34/43] Fix frontend by adding integer conversion --- .../ndcs-country-accordion/ndcs-country-accordion-selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js index 56671f9196..1f0c3feb23 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js @@ -14,7 +14,7 @@ export const parseIndicatorsDefs = createSelector( const parsedIndicators = {}; Object.keys(categories).forEach(category => { const categoryIndicators = indicators.filter( - indicator => indicator.category_ids.indexOf(category) > -1 + indicator => indicator.category_ids.indexOf(parseInt(category, 10)) > -1 ); const parsedDefinitions = categoryIndicators.map(def => { const descriptions = countries.map(country => ({ From 5a08d4fd551baae1e90d48a1584ea29b58db4a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Thu, 2 Nov 2017 17:50:04 +0000 Subject: [PATCH 35/43] Remove code added by rebase --- lib/tasks/cait_indc.rake | 10 ---------- lib/tasks/global_indc.rake | 10 ---------- lib/tasks/wb_indc.rake | 10 ---------- 3 files changed, 30 deletions(-) delete mode 100644 lib/tasks/cait_indc.rake delete mode 100644 lib/tasks/global_indc.rake delete mode 100644 lib/tasks/wb_indc.rake diff --git a/lib/tasks/cait_indc.rake b/lib/tasks/cait_indc.rake deleted file mode 100644 index 54259ae573..0000000000 --- a/lib/tasks/cait_indc.rake +++ /dev/null @@ -1,10 +0,0 @@ -namespace :cait_indc do - desc 'Imports the CAIT INDC dataset from the csv sources' - task import: :environment do - puts "######################################" - puts "# Starting to import CaitIndc data #" - puts "######################################" - ImportCaitIndc.new.call - puts "############## ENDED #################" - end -end diff --git a/lib/tasks/global_indc.rake b/lib/tasks/global_indc.rake deleted file mode 100644 index f2ed7102c3..0000000000 --- a/lib/tasks/global_indc.rake +++ /dev/null @@ -1,10 +0,0 @@ -namespace :global_indc do - desc 'Imports the WB/CAIT global dataset from the csv sources' - task import: :environment do - puts "######################################" - puts "# Starting to import GlobalIndc data#" - puts "######################################" - ImportGlobalIndc.new.call - puts "############## ENDED #################" - end -end diff --git a/lib/tasks/wb_indc.rake b/lib/tasks/wb_indc.rake deleted file mode 100644 index 94902c4f0f..0000000000 --- a/lib/tasks/wb_indc.rake +++ /dev/null @@ -1,10 +0,0 @@ -namespace :wb_indc do - desc 'Imports the WB INDC dataset from the csv sources' - task import: :environment do - puts "####################################" - puts "# Starting to import WbIndc data #" - puts "####################################" - ImportWbIndc.new.call - puts "############## ENDED #################" - end -end From 738e61659517e0e23e6e9d63b88a7f68c5358ba1 Mon Sep 17 00:00:00 2001 From: j8seangel Date: Thu, 2 Nov 2017 19:08:33 +0100 Subject: [PATCH 36/43] Update browser list to match internet explorer --- .babelrc | 2 +- .postcssrc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.babelrc b/.babelrc index 586206a367..f02bf8cf38 100644 --- a/.babelrc +++ b/.babelrc @@ -16,7 +16,7 @@ { "modules": false, "targets": { - "browsers": "> 1%", + "browsers": ["> 1%", "last 2 versions", "Firefox ESR", "Safari >= 8", "IE >= 11"], "uglify": true }, "useBuiltIns": true diff --git a/.postcssrc b/.postcssrc index a881ef805a..28bebf3c72 100644 --- a/.postcssrc +++ b/.postcssrc @@ -1,7 +1,7 @@ { "plugins": { "postcss-cssnext": { - "browsers": ["last 2 versions", "IE > 10"] + "browsers": ["> 1%", "last 2 versions", "Firefox ESR", "Safari >= 8", "IE >= 11"] }, "autoprefixer": { "grid": true From ccd0329bf78527786ff02a84e5dad1f0aba2d41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Thu, 2 Nov 2017 19:13:23 +0000 Subject: [PATCH 37/43] Add parent category to category model, db and import process --- app/models/indc/category.rb | 5 +++++ app/services/import_indc.rb | 21 +++++++++++++++++++ ...171027164658_create_new_indc_categories.rb | 4 ++++ db/schema.rb | 3 +++ 4 files changed, 33 insertions(+) diff --git a/app/models/indc/category.rb b/app/models/indc/category.rb index e6490bf6ed..90c0477120 100644 --- a/app/models/indc/category.rb +++ b/app/models/indc/category.rb @@ -1,6 +1,11 @@ module Indc class Category < ApplicationRecord belongs_to :category_type, class_name: 'Indc::CategoryType' + belongs_to :parent, class_name: 'Indc::Category', + foreign_key: :parent_id, + required: false + has_many :children, class_name: 'Indc::Category', + foreign_key: :parent_id has_and_belongs_to_many :indicators, join_table: :indc_indicators_categories diff --git a/app/services/import_indc.rb b/app/services/import_indc.rb index 1137855775..56e42370ac 100644 --- a/app/services/import_indc.rb +++ b/app/services/import_indc.rb @@ -21,6 +21,7 @@ def call import_sources import_category_types import_categories + import_category_relations import_indicators import_indicators_categories import_labels @@ -147,6 +148,26 @@ def import_categories end end + def import_category_relations + @metadata.each do |r| + global_category = Indc::Category.find_by( + slug: Slug.create(r[:global_category]) + ) or next + + overview_category = Indc::Category.find_by( + slug: Slug.create(r[:overview_category]) + ) if r[:overview_category] + + map_category = Indc::Category.find_by( + slug: Slug.create(r[:map_category]) + ) if r[:map_category] + + global_category.children << [ + overview_category, map_category + ].select(&:itself) + end + end + def import_indicators @metadata. map { |r| [[r[:column_name], r[:source]], r] }. diff --git a/db/migrate/20171027164658_create_new_indc_categories.rb b/db/migrate/20171027164658_create_new_indc_categories.rb index 76ce5a1952..ce2ac0bd7d 100644 --- a/db/migrate/20171027164658_create_new_indc_categories.rb +++ b/db/migrate/20171027164658_create_new_indc_categories.rb @@ -5,6 +5,10 @@ def change to_table: :indc_category_types, on_delete: :cascade }, null: false + t.references :parent, foreign_key: { + to_table: :indc_categories, + on_delete: :cascade + } t.text :slug, null: false t.text :name, null: false t.timestamps diff --git a/db/schema.rb b/db/schema.rb index b98ba30b45..3bf885435f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -82,11 +82,13 @@ create_table "indc_categories", force: :cascade do |t| t.bigint "category_type_id", null: false + t.bigint "parent_id" t.text "slug", null: false t.text "name", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["category_type_id"], name: "index_indc_categories_on_category_type_id" + t.index ["parent_id"], name: "index_indc_categories_on_parent_id" t.index ["slug", "category_type_id"], name: "index_indc_categories_on_slug_and_category_type_id", unique: true end @@ -367,6 +369,7 @@ add_foreign_key "historical_emissions_records", "locations", on_delete: :cascade add_foreign_key "historical_emissions_sectors", "historical_emissions_data_sources", column: "data_source_id", on_delete: :cascade add_foreign_key "historical_emissions_sectors", "historical_emissions_sectors", column: "parent_id", on_delete: :cascade + add_foreign_key "indc_categories", "indc_categories", column: "parent_id", on_delete: :cascade add_foreign_key "indc_categories", "indc_category_types", column: "category_type_id", on_delete: :cascade add_foreign_key "indc_indicators", "indc_sources", column: "source_id", on_delete: :cascade add_foreign_key "indc_indicators_categories", "indc_categories", column: "category_id", on_delete: :cascade From 6c0567ee35118c992b13ee56fd4ca4a0810b42c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Thu, 2 Nov 2017 21:35:17 +0000 Subject: [PATCH 38/43] Update import process and ndcs controller --- app/controllers/api/v1/ndcs_controller.rb | 76 +++++++++++++++++------ app/services/import_indc.rb | 33 ++++++---- 2 files changed, 78 insertions(+), 31 deletions(-) diff --git a/app/controllers/api/v1/ndcs_controller.rb b/app/controllers/api/v1/ndcs_controller.rb index 9060afaac4..4b182e3650 100644 --- a/app/controllers/api/v1/ndcs_controller.rb +++ b/app/controllers/api/v1/ndcs_controller.rb @@ -21,16 +21,43 @@ module V1 class NdcsController < ApiController def index - categories = ::Indc::Category. - includes(:category_type). + sectors = ::Indc::Sector. all - sectors = ::Indc::Sector.all + + categories = ::Indc::Category. + includes(:category_type) if params[:filter] categories = categories.where( - indc_category_types: { - name: params[:filter] - } + indc_category_types: { name: params[:filter] }, + ) + end + + if params[:category] + parent = ::Indc::Category. + includes(:category_type). + where( + indc_category_types: { name: 'global' }, + slug: params[:category] + ) + + categories = categories + .where( + parent_id: parent.map(&:id) + ) + end + + + indicators = ::Indc::Indicator. + includes( + :labels, :source, :categories, + values: [:sector, :label, :location] + ). + where(id: categories.flat_map(&:indicator_ids).uniq) + + if location_list + indicators = indicators.where( + values: {locations: {iso_code3: location_list}} ) end @@ -66,7 +93,7 @@ def content_overview private - def indicators + def load_indicators indicators = ::Indc::Indicator.includes( :labels, :source, @@ -74,25 +101,36 @@ def indicators values: [:location] ) - if location_list - indicators = indicators.where( - values: {locations: {iso_code3: location_list}} - ) - end - if params[:filter] - indicators = indicators.where( + if params[:category] and params[:filter] + indicators2 = indicators.where( + indc_category_types: { + name: 'global' + }, + indc_categories: { + slug: [params[:category], params[:filter]] + } + ) + elsif params[:category] + indicators2 = indicators.where( + indc_category_types: { + name: 'global' + }, + indc_categories: { + slug: params[:category] + } + ) + elsif params[:filter] + indicators2 = indicators.where( indc_category_types: { name: params[:filter] } ) end - if params[:category] - - end - - indicators + indicators.where( + id: indicators2.map(&:id) + ) end def location_list diff --git a/app/services/import_indc.rb b/app/services/import_indc.rb index 56e42370ac..e2a5238ece 100644 --- a/app/services/import_indc.rb +++ b/app/services/import_indc.rb @@ -105,7 +105,7 @@ def value_wb_attributes(row, location, indicator) def submission_attributes(submission) { - location: Location.find_by(iso_code3: submission[:iso]), + location: Location.find_by!(iso_code3: submission[:iso]), submission_type: submission[:type], language: submission[:language], submission_date: submission[:date_of_submission], @@ -150,17 +150,26 @@ def import_categories def import_category_relations @metadata.each do |r| - global_category = Indc::Category.find_by( - slug: Slug.create(r[:global_category]) - ) or next - - overview_category = Indc::Category.find_by( - slug: Slug.create(r[:overview_category]) - ) if r[:overview_category] - - map_category = Indc::Category.find_by( - slug: Slug.create(r[:map_category]) - ) if r[:map_category] + global_category = Indc::Category. + includes(:category_type). + find_by( + slug: Slug.create(r[:global_category]), + indc_category_types: { name: 'global' } + ) or next + + overview_category = Indc::Category. + includes(:category_type). + find_by( + slug: Slug.create(r[:overview_category]), + indc_category_types: { name: 'overview' } + ) if r[:overview_category] + + map_category = Indc::Category. + includes(:category_type). + find_by( + slug: Slug.create(r[:map_category]), + indc_category_types: { name: 'map' } + ) if r[:map_category] global_category.children << [ overview_category, map_category From a91b3d4020f546282e29fa64267bccde0f9cd9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Thu, 2 Nov 2017 21:35:39 +0000 Subject: [PATCH 39/43] Update frontend selectors --- .../ndcs-country-accordion-selectors.js | 2 +- .../app/components/ndcs-table/ndcs-table-selectors.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js index 1f0c3feb23..7c0fd485fe 100644 --- a/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js +++ b/app/javascript/app/components/ndcs-country-accordion/ndcs-country-accordion-selectors.js @@ -40,7 +40,7 @@ export const parseIndicatorsDefsWithSectors = createSelector( const parsedIndicators = []; Object.keys(categories).forEach(category => { const indicatorsWithCategory = indicators.filter( - indicator => indicator.category_ids.indexOf(category) > -1 + indicator => indicator.category_ids.indexOf(parseInt(category, 10)) > -1 ); const parsedDefinitions = indicatorsWithCategory.map(indicator => { const sectorIds = []; diff --git a/app/javascript/app/components/ndcs-table/ndcs-table-selectors.js b/app/javascript/app/components/ndcs-table/ndcs-table-selectors.js index eb1cfd05e6..47384425b6 100644 --- a/app/javascript/app/components/ndcs-table/ndcs-table-selectors.js +++ b/app/javascript/app/components/ndcs-table/ndcs-table-selectors.js @@ -42,7 +42,8 @@ export const getSelectedCategory = createSelector( } const firstCategorywithIndicators = categories.find(category => indicators.some( - indicator => indicator.category_ids.indexOf(category.id) > -1 + indicator => + indicator.category_ids.indexOf(parseInt(category.id, 10)) > -1 ) ); return firstCategorywithIndicators || categories[0]; @@ -54,9 +55,8 @@ export const getSelectedCategory = createSelector( export const getCategoryIndicators = createSelector( [getindicatorsParsed, getSelectedCategory], (indicatorsParsed, category) => { - const categoryId = category.id; const categoryIndicators = indicatorsParsed.filter( - indicator => indicator.categoryIds.indexOf(categoryId) > -1 + indicator => indicator.categoryIds.indexOf(parseInt(category.id, 10)) > -1 ); return categoryIndicators; } From f3a760655137d4f1df7001fa421e9ab621fcc032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=CC=81rio=20Carneiro?= Date: Thu, 2 Nov 2017 22:40:36 +0000 Subject: [PATCH 40/43] Update NDC import process and controller --- app/controllers/api/v1/ndcs_controller.rb | 40 ----------------------- app/services/import_indc.rb | 18 +++++----- 2 files changed, 9 insertions(+), 49 deletions(-) diff --git a/app/controllers/api/v1/ndcs_controller.rb b/app/controllers/api/v1/ndcs_controller.rb index 4b182e3650..db78d45bc0 100644 --- a/app/controllers/api/v1/ndcs_controller.rb +++ b/app/controllers/api/v1/ndcs_controller.rb @@ -93,46 +93,6 @@ def content_overview private - def load_indicators - indicators = ::Indc::Indicator.includes( - :labels, - :source, - categories: [:category_type], - values: [:location] - ) - - - if params[:category] and params[:filter] - indicators2 = indicators.where( - indc_category_types: { - name: 'global' - }, - indc_categories: { - slug: [params[:category], params[:filter]] - } - ) - elsif params[:category] - indicators2 = indicators.where( - indc_category_types: { - name: 'global' - }, - indc_categories: { - slug: params[:category] - } - ) - elsif params[:filter] - indicators2 = indicators.where( - indc_category_types: { - name: params[:filter] - } - ) - end - - indicators.where( - id: indicators2.map(&:id) - ) - end - def location_list if params[:location].blank? nil diff --git a/app/services/import_indc.rb b/app/services/import_indc.rb index e2a5238ece..4604cd89b0 100644 --- a/app/services/import_indc.rb +++ b/app/services/import_indc.rb @@ -75,9 +75,9 @@ def category_attributes(name, category_type) def indicator_attributes(indicator) { - name: indicator[:column_name], - slug: Slug.create(indicator[:column_name]), - description: indicator[:long_name], + name: indicator[:long_name], + slug: indicator[:column_name], + description: indicator[:definition], source: @sources_index[indicator[:source]] } end @@ -87,10 +87,10 @@ def value_cait_attributes(row, location, indicator) location: location, indicator: indicator, label: Indc::Label.find_by( - value: row[:"#{indicator.name}_label"], + value: row[:"#{indicator.slug}_label"], indicator: indicator ), - value: row[:"#{indicator.name}"] + value: row[:"#{indicator.slug}"] } end @@ -192,7 +192,7 @@ def import_indicators_categories indicator = Indc::Indicator. includes(:categories). find_by!( - name: r[:column_name], + slug: r[:column_name], source: @sources_index[r[:source]] ) @@ -225,7 +225,7 @@ def import_labels to_h indicators.each do |indicator_name, labels| - indicator = Indc::Indicator.find_by!(name: indicator_name) + indicator = Indc::Indicator.find_by!(slug: indicator_name) labels.each_with_index do |label, index| Indc::Label.create!( indicator: indicator, @@ -245,7 +245,7 @@ def import_values_cait next end - next unless r[:"#{indicator.name}"] + next unless r[:"#{indicator.slug}"] Indc::Value.create!( value_cait_attributes(r, location, indicator) @@ -276,7 +276,7 @@ def import_sectors def import_values_wb indicator_index = Indc::Indicator. where(source: @sources_index['WB']). - group_by(&:name). + group_by(&:slug). map { |k, v| [k, v.first] }. to_h From 4b2971cedbf0cc1234cbfc289b0458891e575896 Mon Sep 17 00:00:00 2001 From: Simao Belchior Date: Thu, 2 Nov 2017 23:53:31 +0100 Subject: [PATCH 41/43] Avoid crashing when there's no label to match value --- app/javascript/app/components/ndcs-map/ndcs-map-selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/app/components/ndcs-map/ndcs-map-selectors.js b/app/javascript/app/components/ndcs-map/ndcs-map-selectors.js index e866d9a43b..d31b97b711 100644 --- a/app/javascript/app/components/ndcs-map/ndcs-map-selectors.js +++ b/app/javascript/app/components/ndcs-map/ndcs-map-selectors.js @@ -121,7 +121,7 @@ export const getPathsWithStyles = createSelector( ? locations[europeSlug] : locations[iso]; - if (countryData) { + if (countryData && countryData.label_id) { const legendData = legendBuckets[countryData.label_id]; const color = getColorByIndex(legendBuckets, legendData.index); const style = { From 31d3aa9cdcafc5d457eab11968323dadd7436623 Mon Sep 17 00:00:00 2001 From: j8seangel Date: Fri, 3 Nov 2017 10:38:56 +0100 Subject: [PATCH 42/43] Fix ie 11 crashes --- app/views/layouts/application.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 15cdbde910..e35396dd21 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -23,12 +23,12 @@ + <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> - <% if @isProduction %> <%= stylesheet_pack_tag 'main' %> <% end %> From 1b278d9416a86b093080dc6f6fec807f304892e3 Mon Sep 17 00:00:00 2001 From: j8seangel Date: Fri, 3 Nov 2017 10:39:04 +0100 Subject: [PATCH 43/43] Hide sectoral for now --- app/javascript/app/routes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/app/routes.js b/app/javascript/app/routes.js index 5bec642ae0..75c217faf7 100644 --- a/app/javascript/app/routes.js +++ b/app/javascript/app/routes.js @@ -107,7 +107,7 @@ export default [ category: 'sectoral_information' }), exact: true, - anchor: true, + anchor: false, label: 'Sectoral Information', param: 'sectoral-information' }, @@ -154,7 +154,7 @@ export default [ compare: true }), exact: true, - anchor: true, + anchor: false, label: 'Sectoral Information', param: 'sectoral-information' },