Merge branch 'master' of github.com:datopian/portal

This commit is contained in:
anuveyatsu 2020-06-18 10:31:38 +06:00
commit 9b957abf06
34 changed files with 1027 additions and 617 deletions

View File

@ -1,3 +1,3 @@
{
"presets": ["next/babel"]
}
}

24
.prettierignore Normal file
View File

@ -0,0 +1,24 @@
node_modules/
# testing
/coverage
# next.js
.next/
/out/
# yarn
yarn-error.log
yarn.lock
# sass
.sass-cache
# misc
sandbox/*
.env
.staging.env
.nyc_output/*
.DS_Store
public/

View File

@ -2,8 +2,8 @@
`Portal` assumes a "decoupled" approach where the frontend is a separate service from the backend and interacts with backend(s) via an API. It can be used with any backend and has out of the box support for [CKAN][]. `portal` is built in Javascript and React on top of the popular [Next.js][] framework.
[CKAN]: https://ckan.org/
[Next.js]: https://nextjs.com/
[ckan]: https://ckan.org/
[next.js]: https://nextjs.com/
Live DEMO: https://portal.datopian1.now.sh
@ -22,13 +22,12 @@ npm init portal-app my-data-portal
```
> NB: Under the hood, this uses the tool called create-next-app, which bootstraps a Next.js app for you. It uses this template through the --example flag.
>
>
> If it doesnt work, please open an issue.
## Guide
### Styling :art:
### Styling :art:
We use Tailwind as a CSS framework. Take a look at `/styles/index.css` to see what we're importing from Tailwind bundle. You can also configure Tailwind using `tailwind.config.js` file.
@ -49,13 +48,13 @@ $ export CMS=http://myblog.wordpress.com
These are the default routes set up in the "starter" app.
* Home `/`
* Search `/search`
* Dataset `/@org/dataset`
* Resource `/@org/dataset/r/resource`
* Organization `/@org`
* Collection (aka group in CKAN) (?) - suggest to merge into org
* Static pages, eg, `/about` etc. from CMS or can do it without external CMS, e.g., in Next.js
- Home `/`
- Search `/search`
- Dataset `/@org/dataset`
- Resource `/@org/dataset/r/resource`
- Organization `/@org`
- Collection (aka group in CKAN) (?) - suggest to merge into org
- Static pages, eg, `/about` etc. from CMS or can do it without external CMS, e.g., in Next.js
### New Routes
@ -77,11 +76,10 @@ Boot the demo portal:
$ yarn dev # or npm run dev
```
Open [http://localhost:3000](http://localhost:3000) to see the home page :tada:
Open [http://localhost:3000](http://localhost:3000) to see the home page :tada:
You can start editing the page by modifying `/pages/index.tsx`. The page auto-updates as you edit the file.
### Tests
We use Jest for running tests:

View File

@ -1,9 +1,9 @@
import React from 'react'
import { render } from '@testing-library/react'
import renderer from 'react-test-renderer'
import Total from '../../../components/search/Total'
import React from 'react';
import { render } from '@testing-library/react';
import renderer from 'react-test-renderer';
import Total from '../../../components/search/Total';
test('📸 of Total component', () => {
const tree = renderer.create(<Total total={2} />).toJSON()
expect(tree).toMatchSnapshot()
})
test('📸 of Total component', () => {
const tree = renderer.create(<Total total={2} />).toJSON();
expect(tree).toMatchSnapshot();
});

View File

@ -1,17 +1,15 @@
import React from 'react'
import { render } from '@testing-library/react'
import renderer from 'react-test-renderer'
import Index from '../../pages/index'
import React from 'react';
import { render } from '@testing-library/react';
import renderer from 'react-test-renderer';
import Index from '../../pages/index';
test('📸 of Home page', () => {
const tree = renderer.create(<Index />).toJSON()
expect(tree).toMatchSnapshot()
})
test('📸 of Home page', () => {
const tree = renderer.create(<Index />).toJSON();
expect(tree).toMatchSnapshot();
});
test('renders text from hero section', () => {
const { getByText } = render(<Index />)
const linkElement = getByText(
/Find, Share and Publish/
)
expect(linkElement).toBeInTheDocument()
})
const { getByText } = render(<Index />);
const linkElement = getByText(/Find, Share and Publish/);
expect(linkElement).toBeInTheDocument();
});

View File

@ -15,22 +15,16 @@ export default function About({ datapackage }) {
</thead>
<tbody>
<tr>
<td className="px-4 py-2">{ datapackage.resources.length }</td>
<td className="px-4 py-2">{ datapackage.size || 'N\A' }</td>
<td className="px-4 py-2">
</td>
<td className="px-4 py-2">{ datapackage.created }</td>
<td className="px-4 py-2">{ datapackage.modified }</td>
<td className="px-4 py-2">
</td>
<td className="px-4 py-2">
</td>
<td className="px-4 py-2">{datapackage.resources.length}</td>
<td className="px-4 py-2">{datapackage.size || 'NA'}</td>
<td className="px-4 py-2"></td>
<td className="px-4 py-2">{datapackage.created}</td>
<td className="px-4 py-2">{datapackage.modified}</td>
<td className="px-4 py-2"></td>
<td className="px-4 py-2"></td>
</tr>
</tbody>
</table>
</>
)
);
}

View File

@ -1,17 +1,26 @@
import Link from 'next/link'
import Link from 'next/link';
export default function Org({ org }) {
return (
<>
{org
? <>
<img src={org.image_url || 'https://datahub.io/static/img/datahub-cube-edited.svg'} className="h-5 w-5 mr-2 inline-block" />
{org ? (
<>
<img
src={
org.image_url ||
'https://datahub.io/static/img/datahub-cube-edited.svg'
}
className="h-5 w-5 mr-2 inline-block"
/>
<Link href={`/@${org.name}`}>
<a className="font-semibold text-primary underline">{ org.title || org.name }</a>
<a className="font-semibold text-primary underline">
{org.title || org.name}
</a>
</Link>
</>
: ''
}
) : (
''
)}
</>
)
);
}

View File

@ -1,4 +1,4 @@
import Link from 'next/link'
import Link from 'next/link';
export default function Resources({ datapackage }) {
return (
@ -16,25 +16,24 @@ export default function Resources({ datapackage }) {
</thead>
<tbody>
{datapackage.resources.map((resource, index) => (
<tr key={index}>
<td className="px-4 py-2">
<Link href={`${datapackage.name}/r/${resource.name}`}>
<a className="underline">{ resource.title || resource.name }</a>
</Link>
</td>
<td className="px-4 py-2">{ resource.format }</td>
<td className="px-4 py-2">{ resource.created }</td>
<td className="px-4 py-2">{ resource.last_modified }</td>
<td className="px-4 py-2">
<Link href={`${datapackage.name}/r/${resource.name}`}>
<a className="underline">Preview</a>
</Link>
</td>
</tr>
)
)}
<tr key={index}>
<td className="px-4 py-2">
<Link href={`${datapackage.name}/r/${resource.name}`}>
<a className="underline">{resource.title || resource.name}</a>
</Link>
</td>
<td className="px-4 py-2">{resource.format}</td>
<td className="px-4 py-2">{resource.created}</td>
<td className="px-4 py-2">{resource.last_modified}</td>
<td className="px-4 py-2">
<Link href={`${datapackage.name}/r/${resource.name}`}>
<a className="underline">Preview</a>
</Link>
</td>
</tr>
))}
</tbody>
</table>
</>
)
);
}

View File

@ -1,13 +1,13 @@
import Link from 'next/link'
import React, { useState } from 'react'
import Link from 'next/link';
import React, { useState } from 'react';
export default function Nav() {
const [open, setOpen] = useState(false)
const [open, setOpen] = useState(false);
const handleClick = (event) => {
event.preventDefault()
setOpen(!open)
}
event.preventDefault();
setOpen(!open);
};
return (
<nav className="flex items-center justify-between flex-wrap bg-white p-4 border-b border-gray-200">
@ -15,25 +15,40 @@ export default function Nav() {
<img src="/images/logo.svg" alt="portal logo" width="40" />
</div>
<div className="block lg:hidden mx-4">
<button onClick={handleClick} className="flex items-center px-3 py-2 border rounded text-gray-700 border-orange-400 hover:text-black hover:border-black">
<svg className="fill-current h-3 w-3" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Menu</title><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"/></svg>
<button
onClick={handleClick}
className="flex items-center px-3 py-2 border rounded text-gray-700 border-orange-400 hover:text-black hover:border-black"
>
<svg
className="fill-current h-3 w-3"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<title>Menu</title>
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" />
</svg>
</button>
</div>
<div className={`${open ? `block` : `hidden`} lg:block`}>
<Link href="/search">
<Link href="/search">
<a className="block mt-4 lg:inline-block lg:mt-0 text-gray-700 hover:text-black mr-6">
Search
</a>
</Link>
<Link href="http://tech.datopian.com/frontend/">
<a className="block mt-4 lg:inline-block lg:mt-0 text-gray-700 hover:text-black mr-6" target="_blank">
<a
className="block mt-4 lg:inline-block lg:mt-0 text-gray-700 hover:text-black mr-6"
target="_blank"
>
Docs
</a>
</Link>
<Link href="https://github.com/datopian/portal">
<a className="inline-block text-sm px-4 py-2 leading-none border rounded text-white bg-black border-black hover:border-gray-700 hover:text-gray-700 hover:bg-white mt-4 lg:mt-0">GitHub</a>
<a className="inline-block text-sm px-4 py-2 leading-none border rounded text-white bg-black border-black hover:border-gray-700 hover:text-gray-700 hover:bg-white mt-4 lg:mt-0">
GitHub
</a>
</Link>
</div>
</nav>
)
);
}

View File

@ -1,4 +1,4 @@
import Link from 'next/link'
import Link from 'next/link';
export default function Recent() {
return (
@ -8,28 +8,46 @@ export default function Recent() {
<div className="border px-4 mb-4 mr-3 border-gray-100 w-5/6 shadow-sm">
<h1 className="text-2xl font-thin">Our World in Data - COVID 19</h1>
<p className="text-gray-500">Dataset</p>
<p>data collected and managed by Our World in Data - COVID 19 pulled from GitHub on 06/10/2020 https://ourworldindata.org/coronavirus</p>
<p>
data collected and managed by Our World in Data - COVID 19 pulled
from GitHub on 06/10/2020 https://ourworldindata.org/coronavirus
</p>
<Link href="/">
<a className="pt-3 flex justify-end text-orange-500"> View Dataset </a>
<a className="pt-3 flex justify-end text-orange-500">
{' '}
View Dataset{' '}
</a>
</Link>
</div>
<div className="border px-4 mb-4 mr-3 border-gray-100 w-5/6 shadow-sm">
<h1 className="text-2xl font-thin">Our World in Data - COVID 19</h1>
<p className="text-gray-500">Dataset</p>
<p>data collected and managed by Our World in Data - COVID 19 pulled from GitHub on 06/10/2020 https://ourworldindata.org/coronavirus</p>
<p>
data collected and managed by Our World in Data - COVID 19 pulled
from GitHub on 06/10/2020 https://ourworldindata.org/coronavirus
</p>
<Link href="/">
<a className="pt-3 flex justify-end text-orange-500"> View Dataset </a>
<a className="pt-3 flex justify-end text-orange-500">
{' '}
View Dataset{' '}
</a>
</Link>
</div>
<div className="border px-4 mb-4 border-gray-100 w-5/6 shadow-sm">
<h1 className="text-2xl font-thin">Our World in Data - COVID 19</h1>
<p className="text-gray-500 mb-2">Dataset</p>
<p>data collected and managed by Our World in Data - COVID 19 pulled from GitHub on 06/10/2020 https://ourworldindata.org/coronavirus</p>
<p>
data collected and managed by Our World in Data - COVID 19 pulled
from GitHub on 06/10/2020 https://ourworldindata.org/coronavirus
</p>
<Link href="/">
<a className="pt-3 flex justify-end text-orange-500"> View Dataset </a>
<a className="pt-3 flex justify-end text-orange-500">
{' '}
View Dataset{' '}
</a>
</Link>
</div>
</div>
</section>
)
);
}

View File

@ -1,4 +1,4 @@
import Link from 'next/link'
import Link from 'next/link';
export default function About({ resource }) {
return (
@ -18,21 +18,23 @@ export default function About({ resource }) {
</thead>
<tbody>
<tr>
<td className="px-4 py-2">{ resource.name || resource.id }</td>
<td className="px-4 py-2">{ resource.title || '' }</td>
<td className="px-4 py-2">{ resource.description || '' }</td>
<td className="px-4 py-2">{ resource.format }</td>
<td className="px-4 py-2">{ resource.size }</td>
<td className="px-4 py-2">{ resource.created }</td>
<td className="px-4 py-2">{ resource.last_modified || '' }</td>
<td className="px-4 py-2">{resource.name || resource.id}</td>
<td className="px-4 py-2">{resource.title || ''}</td>
<td className="px-4 py-2">{resource.description || ''}</td>
<td className="px-4 py-2">{resource.format}</td>
<td className="px-4 py-2">{resource.size}</td>
<td className="px-4 py-2">{resource.created}</td>
<td className="px-4 py-2">{resource.last_modified || ''}</td>
<td className="px-4 py-2">
<Link href={resource.path}>
<a className="bg-white hover:bg-gray-200 border text-black font-semibold py-2 px-4 rounded">{ resource.format }</a>
<a className="bg-white hover:bg-gray-200 border text-black font-semibold py-2 px-4 rounded">
{resource.format}
</a>
</Link>
</td>
</tr>
</tbody>
</table>
</>
)
);
}

View File

@ -1,5 +1,5 @@
import Link from 'next/link'
import Link from 'next/link';
export default function DataExplorer({ resource }) {
return <>{JSON.stringify(resource)}</>
return <>{JSON.stringify(resource)}</>;
}

View File

@ -1,35 +1,40 @@
import { useState } from 'react'
import { useRouter } from 'next/router'
import Link from 'next/link'
import { useState } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
export default function Input({ query }) {
const router = useRouter()
const [q, setQ] = useState(query.q)
const router = useRouter();
const [q, setQ] = useState(query.q);
const handleChange = (event) => {
setQ(event.target.value)
}
setQ(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault()
event.preventDefault();
router.push({
pathname: '/search',
query: { q },
})
}
});
};
return (
<form onSubmit={handleSubmit} className="flex items-center">
<input
type='text'
name='q'
value={q}
onChange={handleChange}
placeholder='Search'
aria-label='Search'
className="bg-white focus:outline-none focus:shadow-outline border border-gray-300 w-1/2 rounded-lg py-2 px-4 block appearance-none leading-normal"
/>
<button onClick={handleSubmit} className="inline-block text-sm px-4 py-3 mx-3 leading-none border rounded text-white bg-black border-black lg:mt-0">Search</button>
type="text"
name="q"
value={q}
onChange={handleChange}
placeholder="Search"
aria-label="Search"
className="bg-white focus:outline-none focus:shadow-outline border border-gray-300 w-1/2 rounded-lg py-2 px-4 block appearance-none leading-normal"
/>
<button
onClick={handleSubmit}
className="inline-block text-sm px-4 py-3 mx-3 leading-none border rounded text-white bg-black border-black lg:mt-0"
>
Search
</button>
</form>
)
);
}

View File

@ -1,21 +1,31 @@
import Link from 'next/link'
import Link from 'next/link';
export default function Item({ datapackage }) {
return (
<div className="mb-6">
<h3 className="text-xl font-semibold">
<Link href={`/@${datapackage.organization ? datapackage.organization.name : 'dataset'}/${datapackage.name}`}>
<a className="text-primary">{ datapackage.title || datapackage.name }</a>
<Link
href={`/@${
datapackage.organization ? datapackage.organization.name : 'dataset'
}/${datapackage.name}`}
>
<a className="text-primary">
{datapackage.title || datapackage.name}
</a>
</Link>
</h3>
<Link href={`/@${datapackage.organization ? datapackage.organization.name : 'dataset'}`}>
<Link
href={`/@${
datapackage.organization ? datapackage.organization.name : 'dataset'
}`}
>
<a className="text-gray-500 block mt-1">
{ datapackage.organization ? datapackage.organization.title : 'dataset' }
{datapackage.organization
? datapackage.organization.title
: 'dataset'}
</a>
</Link>
<div className="leading-relaxed mt-2">
{ datapackage.description }
</div>
<div className="leading-relaxed mt-2">{datapackage.description}</div>
</div>
)
);
}

View File

@ -1,9 +1,11 @@
import Item from './Item'
import Item from './Item';
export default function List({ datapackages }) {
return (
<ul>
{datapackages.map((datapackage, index) => <Item datapackage={datapackage} key={index} />)}
{datapackages.map((datapackage, index) => (
<Item datapackage={datapackage} key={index} />
))}
</ul>
)
);
}

View File

@ -10,5 +10,5 @@ export default function Sort() {
<option value="views_recent:desc">Popular</option>
</select>
</div>
)
);
}

View File

@ -1,7 +1,7 @@
export default function Total({ total }) {
return (
<h1 className="text-3xl font-semibold text-primary my-6 inline-block">
{ total } results found
{total} results found
</h1>
)
);
}

View File

@ -1,15 +1,14 @@
'use strict'
'use strict';
const nconf = require('nconf')
require('dotenv').config({path: process.env.DOTENV_PATH || '.env'})
const nconf = require('nconf');
require('dotenv').config({ path: process.env.DOTENV_PATH || '.env' });
nconf.argv()
.env()
nconf.argv().env();
nconf.use('memory')
nconf.use('memory');
const dms = (process.env.DMS || 'http://mock.ckan').replace(/\/?$/, '')
const cms = (process.env.CMS || 'http://mock.cms').replace(/\/?$/, '')
const dms = (process.env.DMS || 'http://mock.ckan').replace(/\/?$/, '');
const cms = (process.env.CMS || 'http://mock.cms').replace(/\/?$/, '');
// This is the object that you want to override in your own local config
nconf.defaults({
@ -17,10 +16,10 @@ nconf.defaults({
debug: process.env.DEBUG || false,
DMS: dms,
CMS: cms,
})
});
module.exports = {
get: nconf.get.bind(nconf),
set: nconf.set.bind(nconf),
reset: nconf.reset.bind(nconf)
}
reset: nconf.reset.bind(nconf),
};

View File

@ -1,8 +1,8 @@
module.exports = {
process() {
return 'module.exports = {};'
return 'module.exports = {};';
},
getCacheKey() {
return 'cssTransform'
return 'cssTransform';
},
}
};

View File

@ -1,29 +1,29 @@
module.exports = {
collectCoverageFrom: [
"**/*.{js,jsx,ts,tsx}",
"!**/*.d.ts",
"!**/node_modules/**",
"!**/config/**",
"!**/coverage/**",
"!**/**.config.js**",
'**/*.{js,jsx,ts,tsx}',
'!**/*.d.ts',
'!**/node_modules/**',
'!**/config/**',
'!**/coverage/**',
'!**/**.config.js**',
],
setupFilesAfterEnv: ["<rootDir>/setupTests.js"],
setupFilesAfterEnv: ['<rootDir>/setupTests.js'],
testPathIgnorePatterns: [
"/node_modules/",
"/.next/",
"/jest.config.js/",
"/tailwind.config.js/",
"<rootDir>/postcss.config.js",
'/node_modules/',
'/.next/',
'/jest.config.js/',
'/tailwind.config.js/',
'<rootDir>/postcss.config.js',
],
transform: {
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
'^.+\\.(js|jsx|ts|tsx)$': '<rootDir>/node_modules/babel-jest',
'^.+\\.css$': '<rootDir>/config/jest/cssTransform.js',
},
transformIgnorePatterns: [
"/node_modules/",
"^.+\\.module\\.(css|sass|scss)$",
'/node_modules/',
'^.+\\.module\\.(css|sass|scss)$',
],
moduleNameMapper: {
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy",
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
},
};

View File

@ -1,142 +1,146 @@
const nock = require('nock')
const nock = require('nock');
const gdp = {
"name": "gdp",
"title": "Country, Regional and World GDP (Gross Domestic Product)",
"notes": "Country, regional and world GDP in current US Dollars ($). Regional means collections of countries e.g. Europe & Central Asia. Data is sourced from the World Bank and turned into a standard normalized CSV.",
"resources": [
name: 'gdp',
title: 'Country, Regional and World GDP (Gross Domestic Product)',
notes:
'Country, regional and world GDP in current US Dollars ($). Regional means collections of countries e.g. Europe & Central Asia. Data is sourced from the World Bank and turned into a standard normalized CSV.',
resources: [
{
"name": "gdp",
"id": "gdp",
"title": "GDP data",
"format": "csv",
"created": "2019-03-07T12:00:36.273495",
"last_modified": "2020-05-07T12:00:36.273495",
"datastore_active": false,
"url": "http://mock.filestore/gdp.csv"
}
name: 'gdp',
id: 'gdp',
title: 'GDP data',
format: 'csv',
created: '2019-03-07T12:00:36.273495',
last_modified: '2020-05-07T12:00:36.273495',
datastore_active: false,
url: 'http://mock.filestore/gdp.csv',
},
],
"organization": {
"title": "World Bank",
"name": "world-bank",
"description": "The World Bank is an international financial institution that provides loans and grants to the governments of poorer countries for the purpose of pursuing capital projects.",
"created": "2019-03-07T11:51:13.758844",
"image_url": "https://github.com/datahq/frontend/raw/master/public/img/avatars/world-bank.jpg"
organization: {
title: 'World Bank',
name: 'world-bank',
description:
'The World Bank is an international financial institution that provides loans and grants to the governments of poorer countries for the purpose of pursuing capital projects.',
created: '2019-03-07T11:51:13.758844',
image_url:
'https://github.com/datahq/frontend/raw/master/public/img/avatars/world-bank.jpg',
},
"metadata_created": "2019-03-07T11:56:19.696257",
"metadata_modified": "2019-03-07T12:03:58.817280"
}
metadata_created: '2019-03-07T11:56:19.696257',
metadata_modified: '2019-03-07T12:03:58.817280',
};
const population = {
"name": "population",
"title": "World population data",
"notes": "Population figures for countries, regions (e.g. Asia) and the world. Data comes originally from World Bank and has been converted into standard CSV.",
"resources": [
name: 'population',
title: 'World population data',
notes:
'Population figures for countries, regions (e.g. Asia) and the world. Data comes originally from World Bank and has been converted into standard CSV.',
resources: [
{
"name": "population",
"id": "population",
"title": "Population data",
"format": "csv",
"created": "2019-03-07T12:00:36.273495",
"last_modified": "2020-05-07T12:00:36.273495",
"datastore_active": true,
"url": "http://mock.filestore/population.csv"
}
name: 'population',
id: 'population',
title: 'Population data',
format: 'csv',
created: '2019-03-07T12:00:36.273495',
last_modified: '2020-05-07T12:00:36.273495',
datastore_active: true,
url: 'http://mock.filestore/population.csv',
},
],
"organization": {
"title": "World Bank",
"name": "world-bank",
"description": "The World Bank is an international financial institution that provides loans and grants to the governments of poorer countries for the purpose of pursuing capital projects.",
"created": "2019-03-07T11:51:13.758844",
"image_url": "https://github.com/datahq/frontend/raw/master/public/img/avatars/world-bank.jpg"
}
}
organization: {
title: 'World Bank',
name: 'world-bank',
description:
'The World Bank is an international financial institution that provides loans and grants to the governments of poorer countries for the purpose of pursuing capital projects.',
created: '2019-03-07T11:51:13.758844',
image_url:
'https://github.com/datahq/frontend/raw/master/public/img/avatars/world-bank.jpg',
},
};
module.exports.initMocks = function() {
module.exports.initMocks = function () {
// Uncomment this line if you want to record API calls
// nock.recorder.rec()
// "package_search" mocks
nock('http://mock.ckan/api/3/action', {'encodedQueryParams':true})
nock('http://mock.ckan/api/3/action', { encodedQueryParams: true })
.persist()
// 1. Call without query.
.get('/package_search?facet.field=organization&facet.field=groups&facet.field=tags&facet.field=res_format&facet.field=license_id&facet.limit=5')
.get(
'/package_search?facet.field=organization&facet.field=groups&facet.field=tags&facet.field=res_format&facet.field=license_id&facet.limit=5'
)
.reply(200, {
"success": true,
"result": {
"count": 2,
"sort": "score desc, metadata_modified desc",
"facets": {},
"results": [
gdp,
population
],
"search_facets": {}
}
success: true,
result: {
count: 2,
sort: 'score desc, metadata_modified desc',
facets: {},
results: [gdp, population],
search_facets: {},
},
})
// 2. Call with `q=gdp` query.
.get('/package_search?q=gdp&facet.field=organization&facet.field=groups&facet.field=tags&facet.field=res_format&facet.field=license_id&facet.limit=5')
.get(
'/package_search?q=gdp&facet.field=organization&facet.field=groups&facet.field=tags&facet.field=res_format&facet.field=license_id&facet.limit=5'
)
.reply(200, {
"success": true,
"result": {
"count": 1,
"sort": "score desc, metadata_modified desc",
"facets": {},
"results": [
gdp
],
"search_facets": {}
}
})
success: true,
result: {
count: 1,
sort: 'score desc, metadata_modified desc',
facets: {},
results: [gdp],
search_facets: {},
},
});
// "package_show" mocks
nock('http://mock.ckan/api/3/action', {'encodedQueryParams':true})
nock('http://mock.ckan/api/3/action', { encodedQueryParams: true })
.persist()
.get('/package_show?id=gdp')
.reply(200, {
"success": true,
"result": gdp
success: true,
result: gdp,
})
.get('/package_show?id=population')
.reply(200, {
"success": true,
"result": population
})
success: true,
result: population,
});
// "datastore_search" mocks
nock('http://mock.ckan/api/3/action', {'encodedQueryParams':true})
nock('http://mock.ckan/api/3/action', { encodedQueryParams: true })
.persist()
.get('/datastore_search?resource_id=population')
.reply(200, {
"success": true,
"result": {
"records": [
success: true,
result: {
records: [
{
"Country Code": "ARB",
"Country Name": "Arab World",
"Value": 92197753,
"Year": 1960
'Country Code': 'ARB',
'Country Name': 'Arab World',
Value: 92197753,
Year: 1960,
},
{
"Country Code": "ARB",
"Country Name": "Arab World",
"Value": 94724510,
"Year": 1961
'Country Code': 'ARB',
'Country Name': 'Arab World',
Value: 94724510,
Year: 1961,
},
{
"Country Code": "ARB",
"Country Name": "Arab World",
"Value": 97334442,
"Year": 1962
}
]
}
})
'Country Code': 'ARB',
'Country Name': 'Arab World',
Value: 97334442,
Year: 1962,
},
],
},
});
// Filestore mocks
nock('http://mock.filestore', {'encodedQueryParams':true})
nock('http://mock.filestore', { encodedQueryParams: true })
.persist()
.get('/gdp.csv')
.reply(200, 'a,b,c\n1,2,3\n4,5,6\n')
}
.reply(200, 'a,b,c\n1,2,3\n4,5,6\n');
};

View File

@ -8,7 +8,9 @@
"start": "next start",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
"test:coverage": "jest --coverage",
"format": "prettier --single-quote --write .",
"pre-commit": "echo 'formating your changes.....' && prettier --single-quote --write"
},
"dependencies": {
"bytes": "^3.1.0",
@ -26,12 +28,23 @@
"@types/react": "^16.9.35",
"babel-jest": "^26.0.1",
"dotenv": "^8.2.0",
"husky": ">=4",
"jest": "^26.0.1",
"lint-staged": ">=10",
"nconf": "^0.10.0",
"nock": "^12.0.3",
"postcss-preset-env": "^6.7.0",
"prettier": "2.0.5",
"react-test-renderer": "^16.13.1",
"tailwindcss": "^1.4.6",
"typescript": "^3.9.3"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,tsx,js,jsx,css,html,md}": "yarn pre-commit"
}
}

View File

@ -1,11 +1,11 @@
import { GetServerSideProps } from 'next'
import config from '../../../config'
import utils from '../../../utils'
import Head from 'next/head'
import Nav from '../../../components/home/Nav'
import About from '../../../components/dataset/About'
import Org from '../../../components/dataset/Org'
import Resources from '../../../components/dataset/Resources'
import { GetServerSideProps } from 'next';
import config from '../../../config';
import utils from '../../../utils';
import Head from 'next/head';
import Nav from '../../../components/home/Nav';
import About from '../../../components/dataset/About';
import Org from '../../../components/dataset/Org';
import Resources from '../../../components/dataset/Resources';
function Dataset({ datapackage }) {
return (
@ -17,21 +17,23 @@ function Dataset({ datapackage }) {
<Nav />
<main className="p-6">
<h1 className="text-3xl font-semibold text-primary mb-2">
{ datapackage.title || datapackage.name }
{datapackage.title || datapackage.name}
</h1>
<Org org={datapackage.organization} />
<About datapackage={datapackage} />
<Resources datapackage={datapackage} />
</main>
</>
)
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const res = await fetch(`${config.get('DMS')}/api/3/action/package_show?id=${context.query.dataset}`)
const ckanResult = (await res.json()).result
const datapackage = utils.ckanToDataPackage(ckanResult)
return { props: { datapackage } }
}
const res = await fetch(
`${config.get('DMS')}/api/3/action/package_show?id=${context.query.dataset}`
);
const ckanResult = (await res.json()).result;
const datapackage = utils.ckanToDataPackage(ckanResult);
return { props: { datapackage } };
};
export default Dataset
export default Dataset;

View File

@ -1,10 +1,10 @@
import { GetServerSideProps } from 'next'
import config from '../../../../../config'
import utils from '../../../../../utils'
import Head from 'next/head'
import Nav from '../../../../../components/home/Nav'
import About from '../../../../../components/resource/About'
import DataExplorer from '../../../../../components/resource/DataExplorer'
import { GetServerSideProps } from 'next';
import config from '../../../../../config';
import utils from '../../../../../utils';
import Head from 'next/head';
import Nav from '../../../../../components/home/Nav';
import About from '../../../../../components/resource/About';
import DataExplorer from '../../../../../components/resource/DataExplorer';
function Resource({ resource }) {
return (
@ -16,24 +16,29 @@ function Resource({ resource }) {
<Nav />
<main className="p-6">
<h1 className="text-3xl font-semibold text-primary mb-2">
{ resource.title || resource.name }
{resource.title || resource.name}
</h1>
<About resource={resource} />
<DataExplorer resource={resource} />
</main>
</>
)
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const res = await fetch(`${config.get('DMS')}/api/3/action/package_show?id=${context.query.dataset}`)
const ckanResult = (await res.json()).result
const res = await fetch(
`${config.get('DMS')}/api/3/action/package_show?id=${context.query.dataset}`
);
const ckanResult = (await res.json()).result;
// Only keep single resource
ckanResult.resources = ckanResult.resources.filter(resource => {
return resource.name === context.query.resource || resource.id === context.query.resource
})
const resourceDescriptor = utils.ckanToDataPackage(ckanResult).resources[0]
return { props: { resource: resourceDescriptor } }
}
ckanResult.resources = ckanResult.resources.filter((resource) => {
return (
resource.name === context.query.resource ||
resource.id === context.query.resource
);
});
const resourceDescriptor = utils.ckanToDataPackage(ckanResult).resources[0];
return { props: { resource: resourceDescriptor } };
};
export default Resource
export default Resource;

View File

@ -1,13 +1,13 @@
/* istanbul ignore file */
import '../styles/index.css'
import '../styles/index.css';
// Setup mocks
if (process.env.NODE_ENV === 'development') {
const mocks = require('../mocks')
mocks.initMocks()
console.warn('You have activated the mocks.')
const mocks = require('../mocks');
mocks.initMocks();
console.warn('You have activated the mocks.');
}
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
return <Component {...pageProps} />;
}

View File

@ -1,7 +1,7 @@
import Head from 'next/head'
import Nav from '../components/home/Nav'
import Recent from '../components/home/Recent'
import Input from '../components/search/Input'
import Head from 'next/head';
import Nav from '../components/home/Nav';
import Recent from '../components/home/Recent';
import Input from '../components/search/Input';
export default function Home() {
return (
@ -13,9 +13,15 @@ export default function Home() {
<Nav />
<section className="flex justify-center items-center flex-col mt-8 mx-4 lg:flex-row">
<div>
<h1 className="text-4xl mb-3 font-thin">Find, Share and Publish <br /> Quality Data with <span className="text-orange-500">Datahub</span>
<h1 className="text-4xl mb-3 font-thin">
Find, Share and Publish <br /> Quality Data with{' '}
<span className="text-orange-500">Datahub</span>
</h1>
<p className="text-md font-light mb-3 w-4/5">At Datahub, we have over thousands of datasets for free and a Premium Data Service for additional or customised data with guaranteed updates.</p>
<p className="text-md font-light mb-3 w-4/5">
At Datahub, we have over thousands of datasets for free and a
Premium Data Service for additional or customised data with
guaranteed updates.
</p>
<Input query={{}} />
</div>
<div className="mt-4">
@ -24,5 +30,5 @@ export default function Home() {
</section>
<Recent />
</div>
)
);
}

View File

@ -1,42 +1,46 @@
import { GetServerSideProps } from 'next'
import querystring from 'querystring'
import config from '../config'
import utils from '../utils'
import Head from 'next/head'
import Nav from '../components/home/Nav'
import Input from '../components/search/Input'
import Total from '../components/search/Total'
import Sort from '../components/search/Sort'
import List from '../components/search/List'
import { GetServerSideProps } from 'next';
import querystring from 'querystring';
import config from '../config';
import utils from '../utils';
import Head from 'next/head';
import Nav from '../components/home/Nav';
import Input from '../components/search/Input';
import Total from '../components/search/Total';
import Sort from '../components/search/Sort';
import List from '../components/search/List';
function Search({ ckanResult, datapackages, query }) {
return (
<>
<Head>
<title>Portal | Search</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Nav />
<main className="p-6">
<Input query={query} />
<Total total={ckanResult.count} />
<Sort />
<List datapackages={datapackages} />
</main>
<Head>
<title>Portal | Search</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Nav />
<main className="p-6">
<Input query={query} />
<Total total={ckanResult.count} />
<Sort />
<List datapackages={datapackages} />
</main>
</>
)
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const query = context.query || {}
const query = context.query || {};
const ckanQuery = querystring.stringify(
utils.convertToCkanSearchQuery(query)
)
const res = await fetch(`${config.get('DMS')}/api/3/action/package_search?${ckanQuery}`)
const ckanResult = (await res.json()).result
const datapackages = ckanResult.results.map(item => utils.ckanToDataPackage(item))
);
const res = await fetch(
`${config.get('DMS')}/api/3/action/package_search?${ckanQuery}`
);
const ckanResult = (await res.json()).result;
const datapackages = ckanResult.results.map((item) =>
utils.ckanToDataPackage(item)
);
return { props: { ckanResult, datapackages, query } }
}
return { props: { ckanResult, datapackages, query } };
};
export default Search
export default Search;

View File

@ -1,3 +1,3 @@
module.exports = {
plugins: ['tailwindcss', 'postcss-preset-env'],
}
};

View File

@ -1 +1 @@
import '@testing-library/jest-dom/extend-expect'
import '@testing-library/jest-dom/extend-expect';

View File

@ -1,3 +1,3 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

View File

@ -1,10 +1,8 @@
module.exports = {
purge: [
'./**/*.tsx'
],
purge: ['./**/*.tsx'],
theme: {
extend: {},
},
variants: {},
plugins: [],
}
};

View File

@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@ -18,12 +14,13 @@
"isolatedModules": true,
"jsx": "preserve"
},
"exclude": [
"node_modules"
],
"exclude": ["node_modules"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx", "setupTests.js", "jest.config.js", "config/jest/cssTransform.js"
"**/*.tsx",
"setupTests.js",
"jest.config.js",
"config/jest/cssTransform.js"
]
}

View File

@ -1,123 +1,123 @@
const { URL } = require('url')
const bytes = require('bytes')
const slugify = require('slugify')
const config = require('../config')
const { URL } = require('url');
const bytes = require('bytes');
const slugify = require('slugify');
const config = require('../config');
module.exports.ckanToDataPackage = function (descriptor) {
// Make a copy
const datapackage = JSON.parse(JSON.stringify(descriptor))
const datapackage = JSON.parse(JSON.stringify(descriptor));
// Lowercase name
datapackage.name = datapackage.name.toLowerCase()
datapackage.name = datapackage.name.toLowerCase();
// Rename notes => description
if (datapackage.notes) {
datapackage.description = datapackage.notes
delete datapackage.notes
datapackage.description = datapackage.notes;
delete datapackage.notes;
}
// Rename ckan_url => homepage
if (datapackage.ckan_url) {
datapackage.homepage = datapackage.ckan_url
delete datapackage.ckan_url
datapackage.homepage = datapackage.ckan_url;
delete datapackage.ckan_url;
}
// Parse license
const license = {}
const license = {};
if (datapackage.license_id) {
license.type = datapackage.license_id
delete datapackage.license_id
license.type = datapackage.license_id;
delete datapackage.license_id;
}
if (datapackage.license_title) {
license.title = datapackage.license_title
delete datapackage.license_title
license.title = datapackage.license_title;
delete datapackage.license_title;
}
if (datapackage.license_url) {
license.url = datapackage.license_url
delete datapackage.license_url
license.url = datapackage.license_url;
delete datapackage.license_url;
}
if (Object.keys(license).length > 0) {
datapackage.license = license
datapackage.license = license;
}
// Parse author and sources
const source = {}
const source = {};
if (datapackage.author) {
source.name = datapackage.author
delete datapackage.author
source.name = datapackage.author;
delete datapackage.author;
}
if (datapackage.author_email) {
source.email = datapackage.author_email
delete datapackage.author_email
source.email = datapackage.author_email;
delete datapackage.author_email;
}
if (datapackage.url) {
source.web = datapackage.url
delete datapackage.url
source.web = datapackage.url;
delete datapackage.url;
}
if (Object.keys(source).length > 0) {
datapackage.sources = [source]
datapackage.sources = [source];
}
// Parse maintainer
const author = {}
const author = {};
if (datapackage.maintainer) {
author.name = datapackage.maintainer
delete datapackage.maintainer
author.name = datapackage.maintainer;
delete datapackage.maintainer;
}
if (datapackage.maintainer_email) {
author.email = datapackage.maintainer_email
delete datapackage.maintainer_email
author.email = datapackage.maintainer_email;
delete datapackage.maintainer_email;
}
if (Object.keys(author).length > 0) {
datapackage.author = author
datapackage.author = author;
}
// Parse tags
if (datapackage.tags) {
datapackage.keywords = []
datapackage.tags.forEach(tag => {
datapackage.keywords.push(tag.name)
})
delete datapackage.tags
datapackage.keywords = [];
datapackage.tags.forEach((tag) => {
datapackage.keywords.push(tag.name);
});
delete datapackage.tags;
}
// Parse extras
// TODO
// Resources
datapackage.resources = datapackage.resources.map(resource => {
datapackage.resources = datapackage.resources.map((resource) => {
if (resource.name) {
resource.title = resource.title || resource.name
resource.name = resource.name.toLowerCase().replace(/ /g, '_')
resource.title = resource.title || resource.name;
resource.name = resource.name.toLowerCase().replace(/ /g, '_');
} else {
resource.name = resource.id
resource.name = resource.id;
}
if (resource.url) {
resource.path = resource.url
delete resource.url
resource.path = resource.url;
delete resource.url;
}
if (!resource.schema) {
// If 'fields' property exists use it as schema fields
if (resource.fields) {
if (typeof(resource.fields) === 'string') {
if (typeof resource.fields === 'string') {
try {
resource.fields = JSON.parse(resource.fields)
resource.fields = JSON.parse(resource.fields);
} catch (e) {
console.log('Could not parse resource.fields')
console.log('Could not parse resource.fields');
}
}
resource.schema = {fields: resource.fields}
delete resource.fields
resource.schema = { fields: resource.fields };
delete resource.fields;
}
}
return resource
})
return resource;
});
return datapackage
}
return datapackage;
};
/*
At the moment, we're considering only following examples of CKAN view:
@ -141,86 +141,98 @@ module.exports.ckanViewToDataPackageView = (ckanView) => {
geojson_view: 'map',
pdf_view: 'document',
image_view: 'web',
webpage_view: 'web'
}
const dataPackageView = JSON.parse(JSON.stringify(ckanView))
dataPackageView.specType = viewTypeToSpecType[ckanView.view_type]
|| dataPackageView.specType
|| 'unsupported'
webpage_view: 'web',
};
const dataPackageView = JSON.parse(JSON.stringify(ckanView));
dataPackageView.specType =
viewTypeToSpecType[ckanView.view_type] ||
dataPackageView.specType ||
'unsupported';
if (dataPackageView.specType === 'dataExplorer') {
dataPackageView.spec = {
widgets: [
{specType: 'table'},
{specType: 'simple'},
{specType: 'tabularmap'}
]
}
{ specType: 'table' },
{ specType: 'simple' },
{ specType: 'tabularmap' },
],
};
} else if (dataPackageView.specType === 'simple') {
const graphTypeConvert = {
lines: 'line',
'lines-and-points': 'lines-and-points',
points: 'points',
bars: 'horizontal-bar',
columns: 'bar'
}
columns: 'bar',
};
dataPackageView.spec = {
group: ckanView.group,
series: Array.isArray(ckanView.series) ? ckanView.series : [ckanView.series],
type: graphTypeConvert[ckanView.graph_type] || 'line'
}
series: Array.isArray(ckanView.series)
? ckanView.series
: [ckanView.series],
type: graphTypeConvert[ckanView.graph_type] || 'line',
};
} else if (dataPackageView.specType === 'tabularmap') {
if (ckanView.map_field_type === 'geojson') {
dataPackageView.spec = {
geomField: ckanView.geojson_field
}
geomField: ckanView.geojson_field,
};
} else {
dataPackageView.spec = {
lonField: ckanView.longitude_field,
latField: ckanView.latitude_field
}
latField: ckanView.latitude_field,
};
}
}
return dataPackageView
}
return dataPackageView;
};
/*
Takes single field descriptor from datastore data dictionary and coverts into
tableschema field descriptor.
*/
module.exports.dataStoreDataDictionaryToTableSchema = (dataDictionary) => {
const internalDataStoreFields = ['_id', '_full_text', '_count']
const internalDataStoreFields = ['_id', '_full_text', '_count'];
if (internalDataStoreFields.includes(dataDictionary.id)) {
return null
return null;
}
const dataDictionaryType2TableSchemaType = {
'text': 'string',
'int': 'integer',
'float': 'number',
'date': 'date',
'time': 'time',
'timestamp': 'datetime',
'bool': 'boolean',
'json': 'object'
}
text: 'string',
int: 'integer',
float: 'number',
date: 'date',
time: 'time',
timestamp: 'datetime',
bool: 'boolean',
json: 'object',
};
const field = {
name: dataDictionary.id,
type: dataDictionaryType2TableSchemaType[dataDictionary.type] || 'any'
}
type: dataDictionaryType2TableSchemaType[dataDictionary.type] || 'any',
};
if (dataDictionary.info) {
const constraintsAttributes = ['required', 'unique', 'minLength', 'maxLength', 'minimum', 'maximum', 'pattern', 'enum']
field.constraints = {}
Object.keys(dataDictionary.info).forEach(key => {
const constraintsAttributes = [
'required',
'unique',
'minLength',
'maxLength',
'minimum',
'maximum',
'pattern',
'enum',
];
field.constraints = {};
Object.keys(dataDictionary.info).forEach((key) => {
if (constraintsAttributes.includes(key)) {
field.constraints[key] = dataDictionary.info[key]
field.constraints[key] = dataDictionary.info[key];
} else {
field[key] = dataDictionary.info[key]
field[key] = dataDictionary.info[key];
}
})
});
}
return field
}
return field;
};
module.exports.convertToStandardCollection = (descriptor) => {
const standard = {
@ -228,20 +240,19 @@ module.exports.convertToStandardCollection = (descriptor) => {
title: '',
summary: '',
image: '',
count: null
}
count: null,
};
standard.name = descriptor.name
standard.title = descriptor.title || descriptor.display_name
standard.summary = descriptor.description || ''
standard.image = descriptor.image_display_url || descriptor.image_url
standard.count = descriptor.package_count || 0
standard.extras = descriptor.extras || []
standard.groups = descriptor.groups || []
return standard
}
standard.name = descriptor.name;
standard.title = descriptor.title || descriptor.display_name;
standard.summary = descriptor.description || '';
standard.image = descriptor.image_display_url || descriptor.image_url;
standard.count = descriptor.package_count || 0;
standard.extras = descriptor.extras || [];
standard.groups = descriptor.groups || [];
return standard;
};
module.exports.convertToCkanSearchQuery = (query) => {
const ckanQuery = {
@ -250,79 +261,87 @@ module.exports.convertToCkanSearchQuery = (query) => {
rows: '',
start: '',
sort: '',
'facet.field': ['organization', 'groups', 'tags', 'res_format', 'license_id'],
'facet.field': [
'organization',
'groups',
'tags',
'res_format',
'license_id',
],
'facet.limit': 5,
'facet.mincount': 0
}
'facet.mincount': 0,
};
// Split by space but ignore spaces within double quotes:
if (query.q) {
query.q.match(/(?:[^\s"]+|"[^"]*")+/g).forEach(part => {
query.q.match(/(?:[^\s"]+|"[^"]*")+/g).forEach((part) => {
if (part.includes(':')) {
ckanQuery.fq += part + ' '
ckanQuery.fq += part + ' ';
} else {
ckanQuery.q += part + ' '
ckanQuery.q += part + ' ';
}
})
ckanQuery.fq = ckanQuery.fq.trim()
ckanQuery.q = ckanQuery.q.trim()
});
ckanQuery.fq = ckanQuery.fq.trim();
ckanQuery.q = ckanQuery.q.trim();
}
if (query.fq) {
ckanQuery.fq = ckanQuery.fq ? ckanQuery.fq + ' ' + query.fq : query.fq
ckanQuery.fq = ckanQuery.fq ? ckanQuery.fq + ' ' + query.fq : query.fq;
}
// standard 'size' => ckan 'rows'
ckanQuery.rows = query.size || ''
ckanQuery.rows = query.size || '';
// standard 'from' => ckan 'start'
ckanQuery.start = query.from || ''
ckanQuery.start = query.from || '';
// standard 'sort' => ckan 'sort'
const sortQueries = []
const sortQueries = [];
if (query.sort && query.sort.constructor == Object) {
for (let [key, value] of Object.entries(query.sort)) {
sortQueries.push(`${key} ${value}`)
sortQueries.push(`${key} ${value}`);
}
ckanQuery.sort = sortQueries.join(',')
ckanQuery.sort = sortQueries.join(',');
} else if (query.sort && query.sort.constructor == String) {
ckanQuery.sort = query.sort.replace(':', ' ')
ckanQuery.sort = query.sort.replace(':', ' ');
} else if (query.sort && query.sort.constructor == Array) {
query.sort.forEach(sort => {
sortQueries.push(sort.replace(':', ' '))
})
ckanQuery.sort = sortQueries.join(',')
query.sort.forEach((sort) => {
sortQueries.push(sort.replace(':', ' '));
});
ckanQuery.sort = sortQueries.join(',');
}
// Facets
ckanQuery['facet.field'] = query['facet.field'] || ckanQuery['facet.field']
ckanQuery['facet.limit'] = query['facet.limit'] || ckanQuery['facet.limit']
ckanQuery['facet.mincount'] = query['facet.mincount'] || ckanQuery['facet.mincount']
ckanQuery['facet.field'] = query['facet.field'] || ckanQuery['facet.field']
ckanQuery['facet.field'] = query['facet.field'] || ckanQuery['facet.field'];
ckanQuery['facet.limit'] = query['facet.limit'] || ckanQuery['facet.limit'];
ckanQuery['facet.mincount'] =
query['facet.mincount'] || ckanQuery['facet.mincount'];
ckanQuery['facet.field'] = query['facet.field'] || ckanQuery['facet.field'];
// Remove attributes with empty string, null or undefined values
Object.keys(ckanQuery).forEach((key) => (!ckanQuery[key]) && delete ckanQuery[key])
return ckanQuery
}
Object.keys(ckanQuery).forEach(
(key) => !ckanQuery[key] && delete ckanQuery[key]
);
return ckanQuery;
};
module.exports.pagination = (c, m) => {
let current = c,
last = m,
delta = 2,
left = current - delta,
right = current + delta + 1,
range = [],
rangeWithDots = [],
l;
last = m,
delta = 2,
left = current - delta,
right = current + delta + 1,
range = [],
rangeWithDots = [],
l;
range.push(1)
range.push(1);
for (let i = c - delta; i <= c + delta; i++) {
if (i >= left && i < right && i < m && i > 1) {
range.push(i);
}
}
range.push(m)
range.push(m);
for (let i of range) {
if (l) {
@ -336,15 +355,13 @@ module.exports.pagination = (c, m) => {
l = i;
}
return rangeWithDots;
}
};
module.exports.processMarkdown = require('markdown-it')({
html: true,
linkify: true,
typographer: true
})
typographer: true,
});
/**
* Process data package attributes prior to display to users.
@ -353,39 +370,41 @@ module.exports.processMarkdown = require('markdown-it')({
* etc.
**/
module.exports.processDataPackage = function (datapackage) {
const newDatapackage = JSON.parse(JSON.stringify(datapackage))
const newDatapackage = JSON.parse(JSON.stringify(datapackage));
if (newDatapackage.description) {
newDatapackage.descriptionHtml = module.exports.processMarkdown
.render(newDatapackage.description)
newDatapackage.descriptionHtml = module.exports.processMarkdown.render(
newDatapackage.description
);
}
if (newDatapackage.readme) {
newDatapackage.readmeHtml = module.exports.processMarkdown
.render(newDatapackage.readme)
newDatapackage.readmeHtml = module.exports.processMarkdown.render(
newDatapackage.readme
);
}
newDatapackage.formats = newDatapackage.formats || []
newDatapackage.formats = newDatapackage.formats || [];
// Per each resource:
newDatapackage.resources.forEach(resource => {
newDatapackage.resources.forEach((resource) => {
if (resource.description) {
resource.descriptionHtml = module.exports.processMarkdown
.render(resource.description)
resource.descriptionHtml = module.exports.processMarkdown.render(
resource.description
);
}
// Normalize format (lowercase)
if (resource.format) {
resource.format = resource.format.toLowerCase()
newDatapackage.formats.push(resource.format)
resource.format = resource.format.toLowerCase();
newDatapackage.formats.push(resource.format);
}
// Convert bytes into human-readable format:
if (resource.size) {
resource.sizeFormatted = bytes(resource.size, {decimalPlaces: 0})
resource.sizeFormatted = bytes(resource.size, { decimalPlaces: 0 });
}
})
return newDatapackage
}
});
return newDatapackage;
};
/**
* Create 'displayResources' property which has:
@ -396,114 +415,127 @@ module.exports.processDataPackage = function (datapackage) {
* slug: slugified name of a resource
**/
module.exports.prepareResourcesForDisplay = function (datapackage) {
const newDatapackage = JSON.parse(JSON.stringify(datapackage))
newDatapackage.displayResources = []
const newDatapackage = JSON.parse(JSON.stringify(datapackage));
newDatapackage.displayResources = [];
newDatapackage.resources.forEach((resource, index) => {
const api = resource.datastore_active
? config.get('API_URL') + 'datastore_search?resource_id=' + resource.id + '&sort=_id asc'
: null
? config.get('API_URL') +
'datastore_search?resource_id=' +
resource.id +
'&sort=_id asc'
: null;
// Use proxy path if datastore/filestore proxies are given:
let proxy, cc_proxy
let proxy, cc_proxy;
try {
const resourceUrl = new URL(resource.path)
if (resourceUrl.host === config.get('PROXY_DATASTORE') && resource.format !== 'pdf') {
proxy = '/proxy/datastore' + resourceUrl.pathname + resourceUrl.search
const resourceUrl = new URL(resource.path);
if (
resourceUrl.host === config.get('PROXY_DATASTORE') &&
resource.format !== 'pdf'
) {
proxy = '/proxy/datastore' + resourceUrl.pathname + resourceUrl.search;
}
if (resourceUrl.host === config.get('PROXY_FILESTORE') && resource.format !== 'pdf') {
proxy = '/proxy/filestore' + resourceUrl.pathname + resourceUrl.search
if (
resourceUrl.host === config.get('PROXY_FILESTORE') &&
resource.format !== 'pdf'
) {
proxy = '/proxy/filestore' + resourceUrl.pathname + resourceUrl.search;
}
// Store a CKAN Classic proxy path
// https://github.com/ckan/ckan/blob/master/ckanext/resourceproxy/plugin.py#L59
const apiUrlObject = new URL(config.get('API_URL'))
cc_proxy = apiUrlObject.origin + `/dataset/${datapackage.id}/resource/${resource.id}/proxy`
const apiUrlObject = new URL(config.get('API_URL'));
cc_proxy =
apiUrlObject.origin +
`/dataset/${datapackage.id}/resource/${resource.id}/proxy`;
} catch (e) {
console.warn(e)
console.warn(e);
}
const displayResource = {
resource,
api, // URI for getting the resource via API, e.g., Datastore. Useful when you want to fetch only 100 rows or similar.
proxy, // alternative for path in case there is CORS issue
cc_proxy,
slug: slugify(resource.name) + '-' + index // Used for anchor links
}
newDatapackage.displayResources.push(displayResource)
})
return newDatapackage
}
slug: slugify(resource.name) + '-' + index, // Used for anchor links
};
newDatapackage.displayResources.push(displayResource);
});
return newDatapackage;
};
/**
* Prepare 'views' property which is used by 'datapackage-views-js' library to
* render visualizations such as tables, graphs and maps.
**/
module.exports.prepareViews = function (datapackage) {
const newDatapackage = JSON.parse(JSON.stringify(datapackage))
newDatapackage.views = newDatapackage.views || []
newDatapackage.resources.forEach(resource => {
const resourceViews = resource.views && resource.views.map(view => {
view.resources = [resource.name]
return view
})
const newDatapackage = JSON.parse(JSON.stringify(datapackage));
newDatapackage.views = newDatapackage.views || [];
newDatapackage.resources.forEach((resource) => {
const resourceViews =
resource.views &&
resource.views.map((view) => {
view.resources = [resource.name];
return view;
});
newDatapackage.views = newDatapackage.views.concat(resourceViews)
})
return newDatapackage
}
newDatapackage.views = newDatapackage.views.concat(resourceViews);
});
return newDatapackage;
};
/**
* Create 'dataExplorers' property which is used by 'data-explorer' library to
* render data explorer widgets.
**/
module.exports.prepareDataExplorers = function (datapackage) {
const newDatapackage = JSON.parse(JSON.stringify(datapackage))
const newDatapackage = JSON.parse(JSON.stringify(datapackage));
newDatapackage.displayResources.forEach((displayResource, idx) => {
newDatapackage.displayResources[idx].dataExplorers = []
displayResource.resource.views && displayResource.resource.views.forEach(view => {
const widgets = []
if (view.specType === 'dataExplorer') {
view.spec.widgets.forEach((widget, index) => {
const widgetNames = {
table: 'Table',
simple: 'Chart',
tabularmap: 'Map'
}
widget = {
name: widgetNames[widget.specType] || 'Widget-' + index,
active: index === 0 ? true : false,
newDatapackage.displayResources[idx].dataExplorers = [];
displayResource.resource.views &&
displayResource.resource.views.forEach((view) => {
const widgets = [];
if (view.specType === 'dataExplorer') {
view.spec.widgets.forEach((widget, index) => {
const widgetNames = {
table: 'Table',
simple: 'Chart',
tabularmap: 'Map',
};
widget = {
name: widgetNames[widget.specType] || 'Widget-' + index,
active: index === 0 ? true : false,
datapackage: {
views: [
{
id: view.id,
specType: widget.specType,
},
],
},
};
widgets.push(widget);
});
} else {
const widget = {
name: view.title || '',
active: true,
datapackage: {
views: [
{
id: view.id,
specType: widget.specType
}
]
}
}
widgets.push(widget)
})
} else {
const widget = {
name: view.title || '',
active: true,
views: [view],
},
};
widgets.push(widget);
}
displayResource.resource.api =
displayResource.resource.api || displayResource.api;
const dataExplorer = JSON.stringify({
widgets,
datapackage: {
views: [view]
}
}
widgets.push(widget)
}
resources: [displayResource.resource],
},
}).replace(/'/g, '&#x27;');
newDatapackage.displayResources[idx].dataExplorers.push(dataExplorer);
});
});
displayResource.resource.api = displayResource.resource.api || displayResource.api
const dataExplorer = JSON.stringify({
widgets,
datapackage: {
resources: [displayResource.resource]
}
}).replace(/'/g, "&#x27;")
newDatapackage.displayResources[idx].dataExplorers.push(dataExplorer)
})
})
return newDatapackage
}
return newDatapackage;
};

282
yarn.lock
View File

@ -1417,6 +1417,11 @@
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/prettier@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.1.tgz#b6e98083f13faa1e5231bfa3bdb1b0feff536b6d"
@ -1713,7 +1718,12 @@ anser@1.4.9:
resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760"
integrity sha512-AI+BjTeGt2+WFk4eWcqbQ7snZpDBt8SaLlj0RT2h5xfdWaiy51OjYvqwMrNzJLGy8iOAL6nKDITWO+rd4MkYEA==
ansi-escapes@^4.2.1:
ansi-colors@^3.2.1:
version "3.2.4"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
@ -1852,6 +1862,11 @@ assign-symbols@^1.0.0:
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
astral-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
async-each@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
@ -2463,6 +2478,21 @@ clean-stack@^2.0.0:
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
dependencies:
restore-cursor "^3.1.0"
cli-truncate@2.1.0, cli-truncate@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
dependencies:
slice-ansi "^3.0.0"
string-width "^4.2.0"
cliui@^3.0.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
@ -2574,7 +2604,7 @@ commander@^2.20.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^5.0.0:
commander@^5.0.0, commander@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
@ -2584,6 +2614,11 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
compare-versions@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
component-emitter@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
@ -2678,6 +2713,17 @@ cosmiconfig@^5.0.0:
js-yaml "^3.13.1"
parse-json "^4.0.0"
cosmiconfig@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
dependencies:
"@types/parse-json" "^4.0.0"
import-fresh "^3.1.0"
parse-json "^5.0.0"
path-type "^4.0.0"
yaml "^1.7.2"
create-ecdh@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff"
@ -3041,6 +3087,11 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@ -3278,6 +3329,13 @@ enhanced-resolve@^4.1.0:
memory-fs "^0.5.0"
tapable "^1.0.0"
enquirer@^2.3.5:
version "2.3.5"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.5.tgz#3ab2b838df0a9d8ab9e7dff235b0e8712ef92381"
integrity sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==
dependencies:
ansi-colors "^3.2.1"
entities@^2.0.0, entities@~2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f"
@ -3432,7 +3490,7 @@ execa@^1.0.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
execa@^4.0.0:
execa@^4.0.0, execa@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.2.tgz#ad87fb7b2d9d564f70d2b62d511bee41d5cbb240"
integrity sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==
@ -3555,6 +3613,13 @@ figgy-pudding@^3.5.1:
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
figures@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
dependencies:
escape-string-regexp "^1.0.5"
file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
@ -3617,6 +3682,13 @@ find-up@^4.0.0, find-up@^4.1.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
find-versions@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e"
integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==
dependencies:
semver-regex "^2.0.0"
flatten@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
@ -3737,6 +3809,11 @@ get-caller-file@^2.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==
get-package-type@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
@ -3980,6 +4057,22 @@ human-signals@^1.1.1:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
husky@>=4:
version "4.2.5"
resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36"
integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==
dependencies:
chalk "^4.0.0"
ci-info "^2.0.0"
compare-versions "^3.6.0"
cosmiconfig "^6.0.0"
find-versions "^3.2.0"
opencollective-postinstall "^2.0.2"
pkg-dir "^4.2.0"
please-upgrade-node "^3.2.0"
slash "^3.0.0"
which-pm-runs "^1.0.0"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -4012,6 +4105,14 @@ import-fresh@^2.0.0:
caller-path "^2.0.0"
resolve-from "^3.0.0"
import-fresh@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==
dependencies:
parent-module "^1.0.0"
resolve-from "^4.0.0"
import-local@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6"
@ -4264,6 +4365,11 @@ is-number@^7.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
is-obj@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
@ -4293,6 +4399,11 @@ is-regex@^1.0.5:
dependencies:
has-symbols "^1.0.1"
is-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
is-resolvable@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
@ -4989,6 +5100,41 @@ linkify-it@^3.0.1:
dependencies:
uc.micro "^1.0.1"
lint-staged@>=10:
version "10.2.11"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.11.tgz#713c80877f2dc8b609b05bc59020234e766c9720"
integrity sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==
dependencies:
chalk "^4.0.0"
cli-truncate "2.1.0"
commander "^5.1.0"
cosmiconfig "^6.0.0"
debug "^4.1.1"
dedent "^0.7.0"
enquirer "^2.3.5"
execa "^4.0.1"
listr2 "^2.1.0"
log-symbols "^4.0.0"
micromatch "^4.0.2"
normalize-path "^3.0.0"
please-upgrade-node "^3.2.0"
string-argv "0.3.1"
stringify-object "^3.3.0"
listr2@^2.1.0:
version "2.1.8"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.1.8.tgz#8af7ebc70cdbe866ddbb6c80909142bd45758f1f"
integrity sha512-Op+hheiChfAphkJ5qUxZtHgyjlX9iNnAeFS/S134xw7mVSg0YVrQo1IY4/K+ElY6XgOPg2Ij4z07urUXR+YEew==
dependencies:
chalk "^4.0.0"
cli-truncate "^2.1.0"
figures "^3.2.0"
indent-string "^4.0.0"
log-update "^4.0.0"
p-map "^4.0.0"
rxjs "^6.5.5"
through "^2.3.8"
loader-runner@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
@ -5089,6 +5235,23 @@ lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
log-symbols@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
dependencies:
chalk "^4.0.0"
log-update@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
dependencies:
ansi-escapes "^4.3.0"
cli-cursor "^3.1.0"
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@ -5747,6 +5910,11 @@ onetime@^5.1.0:
dependencies:
mimic-fn "^2.1.0"
opencollective-postinstall@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
optionator@^0.8.1:
version "0.8.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
@ -5823,6 +5991,13 @@ p-map@^3.0.0:
dependencies:
aggregate-error "^3.0.0"
p-map@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
dependencies:
aggregate-error "^3.0.0"
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
@ -5847,6 +6022,13 @@ parallel-transform@^1.1.0:
inherits "^2.0.3"
readable-stream "^2.1.5"
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
dependencies:
callsites "^3.0.0"
parse-asn1@^5.0.0, parse-asn1@^5.1.5:
version "5.1.5"
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e"
@ -5927,6 +6109,11 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
pbkdf2@^3.0.3:
version "3.1.1"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94"
@ -5986,6 +6173,13 @@ platform@1.3.3:
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.3.tgz#646c77011899870b6a0903e75e997e8e51da7461"
integrity sha1-ZGx3ARiZhwtqCQPnXpl+jlHadGE=
please-upgrade-node@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
dependencies:
semver-compare "^1.0.0"
pnp-webpack-plugin@1.6.4:
version "1.6.4"
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
@ -6679,6 +6873,11 @@ prepend-http@^1.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
prettier@2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4"
integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==
pretty-format@^25.2.1, pretty-format@^25.5.0:
version "25.5.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
@ -7128,6 +7327,11 @@ resolve-from@^3.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
integrity sha1-six699nWiBvItuZTM17rywoYh0g=
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve-from@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
@ -7161,6 +7365,14 @@ resolve@^1.10.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.8.
dependencies:
path-parse "^1.0.6"
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
dependencies:
onetime "^5.1.0"
signal-exit "^3.0.2"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@ -7223,6 +7435,13 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rxjs@^6.5.5:
version "6.5.5"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
dependencies:
tslib "^1.9.0"
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@ -7322,6 +7541,16 @@ secure-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca"
integrity sha1-8MgtmKOxOah3aogIBQuCRDEIf8o=
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
semver-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338"
integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@ -7440,6 +7669,24 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
slice-ansi@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
dependencies:
ansi-styles "^4.0.0"
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
slice-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
dependencies:
ansi-styles "^4.0.0"
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
slugify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.4.0.tgz#c9557c653c54b0c7f7a8e786ef3431add676d2cb"
@ -7670,6 +7917,11 @@ strict-uri-encode@^1.0.0:
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
string-argv@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
string-hash@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
@ -7749,6 +8001,15 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
stringify-object@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
dependencies:
get-own-enumerable-property-symbols "^3.0.0"
is-obj "^1.0.1"
is-regexp "^1.0.0"
strip-ansi@6.0.0, strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
@ -7977,6 +8238,11 @@ through2@^2.0.0:
readable-stream "~2.3.6"
xtend "~4.0.1"
through@^2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
timers-browserify@^2.0.4:
version "2.0.11"
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f"
@ -8494,6 +8760,11 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which-pm-runs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
which@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
@ -8604,6 +8875,11 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^1.7.2:
version "1.10.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
yargs-parser@^18.1.1:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"