Revert "[@portaljs/components][xl] - @portaljs/ckan package"
This reverts commit 91c76c213c.
This commit is contained in:
Submodule examples/ckan deleted from 7371c21346
1
examples/ckan-example/.env
Normal file
1
examples/ckan-example/.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DMS=https://demo.dev.datopian.com
|
||||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
@@ -1,16 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es2020: true
|
|
||||||
},
|
|
||||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 'latest',
|
|
||||||
sourceType: 'module'
|
|
||||||
},
|
|
||||||
plugins: ['react-refresh'],
|
|
||||||
rules: {
|
|
||||||
'react-refresh/only-export-components': 'warn'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
24
packages/ckan/.gitignore
vendored
24
packages/ckan/.gitignore
vendored
@@ -1,24 +0,0 @@
|
|||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# PortalJS React Components
|
|
||||||
|
|
||||||
**Storybook:** https://storybook.portaljs.org
|
|
||||||
**Docs**: https://portaljs.org/docs
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
To install this package on your project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm i @portaljs/components
|
|
||||||
```
|
|
||||||
|
|
||||||
> Note: React 18 is required.
|
|
||||||
|
|
||||||
You'll also have to import the styles CSS file in your project:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// E.g.: Next.js => pages/_app.tsx
|
|
||||||
import '@portaljs/components/styles.css'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dev
|
|
||||||
|
|
||||||
Use Storybook to work on components by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run storybook
|
|
||||||
```
|
|
||||||
24959
packages/ckan/package-lock.json
generated
24959
packages/ckan/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@portaljs/ckan",
|
|
||||||
"version": "0.1.2",
|
|
||||||
"type": "module",
|
|
||||||
"description": "https://portaljs.org",
|
|
||||||
"keywords": [
|
|
||||||
"data portal",
|
|
||||||
"data catalog",
|
|
||||||
"table",
|
|
||||||
"charts",
|
|
||||||
"visualization"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc && vite build && npm run build-tailwind",
|
|
||||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
||||||
"prepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies\"",
|
|
||||||
"build-tailwind": "NODE_ENV=production npx tailwindcss -o ./dist/styles.css --minify"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"main": "./dist/components.umd.js",
|
|
||||||
"module": "./dist/components.es.js",
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"import": "./dist/components.es.js",
|
|
||||||
"require": "./dist/components.umd.js"
|
|
||||||
},
|
|
||||||
"./styles.css": {
|
|
||||||
"import": "./dist/styles.css"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import Link from "next/link";
|
|
||||||
import { format } from "timeago.js";
|
|
||||||
import { Dataset } from "../interfaces/dataset.interface";
|
|
||||||
import ResourceCard from "./ResourceCard";
|
|
||||||
|
|
||||||
export default function DatasetCard({
|
|
||||||
dataset,
|
|
||||||
showOrg = true,
|
|
||||||
urlPrefix = ""
|
|
||||||
}: {
|
|
||||||
dataset: Dataset;
|
|
||||||
showOrg: boolean;
|
|
||||||
urlPrefix?: string;
|
|
||||||
}) {
|
|
||||||
const resourceBgColors = {
|
|
||||||
PDF: "bg-cyan-300",
|
|
||||||
CSV: "bg-emerald-300",
|
|
||||||
JSON: "bg-yellow-300",
|
|
||||||
ODS: "bg-amber-400",
|
|
||||||
XLS: "bg-orange-300",
|
|
||||||
DOC: "bg-red-300",
|
|
||||||
SHP: "bg-purple-400",
|
|
||||||
HTML: "bg-pink-300",
|
|
||||||
};
|
|
||||||
|
|
||||||
const resourceBgColorsProxy = new Proxy(resourceBgColors, {
|
|
||||||
get: (obj, prop) => {
|
|
||||||
if (prop in obj) {
|
|
||||||
return obj[prop]
|
|
||||||
}
|
|
||||||
return "bg-amber-400"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function DatasetInformations() {
|
|
||||||
return (
|
|
||||||
<div className="flex align-center gap-2">
|
|
||||||
{(dataset.resources.length > 0 && dataset.resources[0].format && (
|
|
||||||
<>
|
|
||||||
{showOrg !== false && (
|
|
||||||
<span
|
|
||||||
className={`${
|
|
||||||
resourceBgColorsProxy[
|
|
||||||
dataset.resources[0].format as keyof typeof resourceBgColors
|
|
||||||
]
|
|
||||||
} px-4 py-1 rounded-full text-xs`}
|
|
||||||
>
|
|
||||||
{dataset.organization
|
|
||||||
? dataset.organization.title
|
|
||||||
: "No organization"}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={`${
|
|
||||||
resourceBgColorsProxy[
|
|
||||||
dataset.resources[0].format as keyof typeof resourceBgColors
|
|
||||||
]
|
|
||||||
} px-4 py-1 rounded-full text-xs`}
|
|
||||||
>
|
|
||||||
{dataset.metadata_created && format(dataset.metadata_created)}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)) || (
|
|
||||||
<>
|
|
||||||
{showOrg !== false && (
|
|
||||||
<span className="bg-gray-200 px-4 py-1 rounded-full text-xs">
|
|
||||||
{dataset.organization
|
|
||||||
? dataset.organization.title
|
|
||||||
: "No organization"}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className="bg-gray-200 px-4 py-1 rounded-full text-xs">
|
|
||||||
{dataset.metadata_created && format(dataset.metadata_created)}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<article className="grid grid-cols-1 md:grid-cols-7 gap-x-2">
|
|
||||||
<ResourceCard
|
|
||||||
resource={dataset?.resources.find((resource) => resource.format)}
|
|
||||||
/>
|
|
||||||
<div className="col-span-6 place-content-start flex flex-col gap-1">
|
|
||||||
<Link href={`${urlPrefix}/@${dataset.organization?.name}/${dataset.name}`}>
|
|
||||||
<h1 className="m-auto md:m-0 font-semibold text-lg text-zinc-900">
|
|
||||||
{dataset.title || "No title"}
|
|
||||||
</h1>
|
|
||||||
</Link>
|
|
||||||
<p className="text-sm font-normal text-stone-500 line-clamp-2 h-[44px] overflow-y-hidden ">
|
|
||||||
{dataset.notes?.replace(/<\/?[^>]+(>|$)/g, "") || "No description"}
|
|
||||||
</p>
|
|
||||||
<DatasetInformations />
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
import { Field, Form, Formik, useFormikContext } from "formik";
|
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
|
||||||
import { PackageSearchOptions, Tag, Group, Organization, FilterObj } from "../interfaces";
|
|
||||||
|
|
||||||
function AutoSubmit({
|
|
||||||
setOptions,
|
|
||||||
options,
|
|
||||||
}: {
|
|
||||||
options: PackageSearchOptions;
|
|
||||||
setOptions: Dispatch<SetStateAction<PackageSearchOptions>>;
|
|
||||||
}) {
|
|
||||||
const { values } = useFormikContext<{
|
|
||||||
tags: string[];
|
|
||||||
orgs: string[];
|
|
||||||
groups: string[];
|
|
||||||
}>();
|
|
||||||
useEffect(() => {
|
|
||||||
setOptions({
|
|
||||||
...options,
|
|
||||||
groups: values.groups,
|
|
||||||
tags: values.tags,
|
|
||||||
orgs: values.orgs,
|
|
||||||
});
|
|
||||||
}, [values]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DatasetSearchFilters({
|
|
||||||
tags,
|
|
||||||
orgs,
|
|
||||||
groups,
|
|
||||||
setOptions,
|
|
||||||
options,
|
|
||||||
filtersName,
|
|
||||||
}: {
|
|
||||||
tags: Array<Tag>;
|
|
||||||
orgs: Array<Organization>;
|
|
||||||
groups: Array<Group>;
|
|
||||||
options: PackageSearchOptions;
|
|
||||||
setOptions: Dispatch<SetStateAction<PackageSearchOptions>>;
|
|
||||||
filtersName?: FilterObj | undefined;
|
|
||||||
}) {
|
|
||||||
const [seeMoreOrgs, setSeeMoreOrgs] = useState(false);
|
|
||||||
const [seeMoreTags, setSeeMoreTags] = useState(false);
|
|
||||||
const [seeMoreGroups, setSeeMoreGroups] = useState(false);
|
|
||||||
return (
|
|
||||||
<Formik
|
|
||||||
initialValues={{
|
|
||||||
tags: [],
|
|
||||||
orgs: [],
|
|
||||||
groups: [],
|
|
||||||
}}
|
|
||||||
onSubmit={async (values) => {
|
|
||||||
alert(JSON.stringify(values, null, 2));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form>
|
|
||||||
<section className="bg-white rounded-lg xl:p-8 p-4 mb-4 max-h-[400px] overflow-y-auto">
|
|
||||||
<h1 className="font-bold pb-4">Refine by {`${filtersName?.org || "Organization"}`}</h1>
|
|
||||||
{orgs
|
|
||||||
.filter((org) => org.title || org.id)
|
|
||||||
.slice(0, seeMoreOrgs ? orgs.length : 5)
|
|
||||||
.map((org) => (
|
|
||||||
<div key={org.id}>
|
|
||||||
<Field
|
|
||||||
type="checkbox"
|
|
||||||
id={org.id}
|
|
||||||
name="orgs"
|
|
||||||
value={org.name}
|
|
||||||
></Field>
|
|
||||||
<label className="ml-1.5" htmlFor={org.id}>
|
|
||||||
{org.title || org.display_name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{orgs.length > 5 && (
|
|
||||||
<button
|
|
||||||
className="bg-gray-300 px-2 rounded text-gray-600 mt-2"
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSeeMoreOrgs(!seeMoreOrgs)}
|
|
||||||
>
|
|
||||||
Show {seeMoreOrgs ? "less" : "more"}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
<section className="bg-white rounded-lg xl:p-8 p-4 mb-4 max-h-[400px] overflow-y-auto">
|
|
||||||
<h1 className="font-bold pb-4">Refine by {`${filtersName?.group || "Theme"}`}</h1>
|
|
||||||
{groups.slice(0, seeMoreGroups ? groups.length : 5).map((group) => (
|
|
||||||
<div key={group.id}>
|
|
||||||
<Field
|
|
||||||
type="checkbox"
|
|
||||||
id={group.id}
|
|
||||||
name="groups"
|
|
||||||
value={group.name}
|
|
||||||
></Field>
|
|
||||||
<label className="ml-1.5" htmlFor={group.id}>
|
|
||||||
{group.display_name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{groups.length > 5 && (
|
|
||||||
<button
|
|
||||||
onClick={() => setSeeMoreGroups(!seeMoreGroups)}
|
|
||||||
type="button"
|
|
||||||
className="bg-gray-300 px-2 rounded text-gray-600 mt-2"
|
|
||||||
>
|
|
||||||
See {seeMoreGroups ? "less" : "more..."}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
<section className="bg-white rounded-lg xl:p-8 p-4 mb-4 max-h-[400px] overflow-y-auto">
|
|
||||||
<h1 className="font-bold pb-4">Refine by Keyword</h1>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
{tags.slice(0, seeMoreTags ? tags.length : 5).map((tag) => (
|
|
||||||
<div key={tag.id}>
|
|
||||||
<Field
|
|
||||||
type="checkbox"
|
|
||||||
className="hidden tag-checkbox"
|
|
||||||
id={tag.id}
|
|
||||||
name="tags"
|
|
||||||
value={tag.name}
|
|
||||||
></Field>
|
|
||||||
<label
|
|
||||||
className="bg-gray-200 px-4 py-1 rounded-full text-xs block"
|
|
||||||
htmlFor={tag.id}
|
|
||||||
>
|
|
||||||
{tag.display_name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{tags.length > 5 && (
|
|
||||||
<button
|
|
||||||
onClick={() => setSeeMoreTags(!seeMoreTags)}
|
|
||||||
type="button"
|
|
||||||
className="bg-gray-300 px-2 rounded text-gray-600 mt-2"
|
|
||||||
>
|
|
||||||
See {seeMoreTags ? "less" : "more..."}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
<AutoSubmit options={options} setOptions={setOptions} />
|
|
||||||
</Form>
|
|
||||||
</Formik>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import { Field, Form, Formik } from 'formik';
|
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
|
||||||
import {
|
|
||||||
PackageSearchOptions,
|
|
||||||
Group,
|
|
||||||
Organization,
|
|
||||||
FilterObj,
|
|
||||||
} from '../interfaces';
|
|
||||||
|
|
||||||
export default function DatasetSearchForm({
|
|
||||||
orgs,
|
|
||||||
groups,
|
|
||||||
setOptions,
|
|
||||||
options,
|
|
||||||
filtersName,
|
|
||||||
}: {
|
|
||||||
orgs: Array<Organization>;
|
|
||||||
groups: Array<Group>;
|
|
||||||
options: PackageSearchOptions;
|
|
||||||
setOptions: Dispatch<SetStateAction<PackageSearchOptions>>;
|
|
||||||
filtersName?: FilterObj | undefined;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Formik
|
|
||||||
initialValues={{
|
|
||||||
org: '',
|
|
||||||
group: '',
|
|
||||||
query: '',
|
|
||||||
}}
|
|
||||||
enableReinitialize={true}
|
|
||||||
onSubmit={async (values) => {
|
|
||||||
const org = orgs.find(
|
|
||||||
(org) => (org.title || org.display_name) === values.org
|
|
||||||
);
|
|
||||||
const group = groups.find(
|
|
||||||
(group) => group.display_name === values.group
|
|
||||||
);
|
|
||||||
setOptions({
|
|
||||||
...options,
|
|
||||||
groups: group ? [group.name] : [],
|
|
||||||
orgs: org ? [org.name] : [],
|
|
||||||
query: values.query,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="mx-auto" style={{ width: 'min(1100px, 95vw)' }}>
|
|
||||||
<Form className="min-h-[80px] flex flex-col lg:flex-row bg-white inline-block px-5 py-3 rounded-xl">
|
|
||||||
<Field
|
|
||||||
type="text"
|
|
||||||
placeholder="Search Datasets"
|
|
||||||
className="mx-4 grow py-4 border-0 placeholder:text-neutral-400"
|
|
||||||
name="query"
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
list="groups"
|
|
||||||
name="group"
|
|
||||||
placeholder={`${filtersName?.group || 'Theme'}`}
|
|
||||||
className="lg:border-l p-4 mx-2 placeholder:text-neutral-400"
|
|
||||||
></Field>
|
|
||||||
|
|
||||||
<datalist aria-label="Formats" id="groups">
|
|
||||||
<option value="">{`${filtersName?.group || 'Theme'}`}</option>
|
|
||||||
{groups.map((group, index) => (
|
|
||||||
<option key={index}>{group.display_name}</option>
|
|
||||||
))}
|
|
||||||
</datalist>
|
|
||||||
<Field
|
|
||||||
list="orgs"
|
|
||||||
name="org"
|
|
||||||
placeholder="Organization"
|
|
||||||
className="lg:border-l p-4 mx-2 placeholder:text-neutral-400"
|
|
||||||
autoComplete="off"
|
|
||||||
></Field>
|
|
||||||
|
|
||||||
<datalist aria-label="Formats" id="orgs">
|
|
||||||
<option value="">Organization</option>
|
|
||||||
{orgs.map((org, index) => (
|
|
||||||
<option key={index}>{org.title || org.display_name}</option>
|
|
||||||
))}
|
|
||||||
</datalist>
|
|
||||||
<button
|
|
||||||
className="font-bold text-black px-12 py-4 rounded-lg bg-accent hover:bg-cyan-500 duration-150"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
SEARCH
|
|
||||||
</button>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</Formik>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import { Dispatch, SetStateAction, useState } from 'react';
|
|
||||||
import useSWR from 'swr';
|
|
||||||
import { SWRConfig } from 'swr';
|
|
||||||
import { PackageSearchOptions } from '../interfaces';
|
|
||||||
import DatasetCard from './DatasetCard';
|
|
||||||
import Pagination from './Pagination';
|
|
||||||
import CKAN from '../lib/ckanapi';
|
|
||||||
|
|
||||||
export default function ListOfDatasets({
|
|
||||||
ckan,
|
|
||||||
options,
|
|
||||||
setOptions,
|
|
||||||
urlPrefix = '',
|
|
||||||
}: {
|
|
||||||
ckan: CKAN;
|
|
||||||
options: PackageSearchOptions;
|
|
||||||
setOptions: Dispatch<SetStateAction<PackageSearchOptions>>;
|
|
||||||
urlPrefix?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<SWRConfig>
|
|
||||||
<ListOfDatasetsInner
|
|
||||||
ckan={ckan}
|
|
||||||
options={options}
|
|
||||||
setOptions={setOptions}
|
|
||||||
urlPrefix={urlPrefix}
|
|
||||||
/>
|
|
||||||
</SWRConfig>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ListOfDatasetsInner({
|
|
||||||
ckan,
|
|
||||||
options,
|
|
||||||
setOptions,
|
|
||||||
urlPrefix = '',
|
|
||||||
}: {
|
|
||||||
ckan: CKAN;
|
|
||||||
options: PackageSearchOptions;
|
|
||||||
setOptions: Dispatch<SetStateAction<PackageSearchOptions>>;
|
|
||||||
urlPrefix?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 gap-4 homepage-padding">
|
|
||||||
<ListItems
|
|
||||||
ckan={ckan}
|
|
||||||
setOptions={setOptions}
|
|
||||||
options={options}
|
|
||||||
urlPrefix={urlPrefix}
|
|
||||||
/>
|
|
||||||
<div style={{ display: 'none' }}>
|
|
||||||
<ListItems
|
|
||||||
ckan={ckan}
|
|
||||||
setOptions={setOptions}
|
|
||||||
options={{ ...options, offset: options.offset + 5 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ListItems({
|
|
||||||
ckan,
|
|
||||||
options,
|
|
||||||
setOptions,
|
|
||||||
urlPrefix = '',
|
|
||||||
}: {
|
|
||||||
ckan: CKAN;
|
|
||||||
options: PackageSearchOptions;
|
|
||||||
setOptions: Dispatch<SetStateAction<PackageSearchOptions>>;
|
|
||||||
urlPrefix?: string;
|
|
||||||
}) {
|
|
||||||
const { data } = useSWR(['package_search', options], async () =>
|
|
||||||
ckan.packageSearch({ ...options })
|
|
||||||
);
|
|
||||||
//Define which page buttons are going to be displayed in the pagination list
|
|
||||||
const [subsetOfPages, setSubsetOfPages] = useState(0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h2 className="text-4xl capitalize font-bold text-zinc-900">
|
|
||||||
{data?.count} Datasets
|
|
||||||
</h2>
|
|
||||||
{data?.datasets?.map((dataset) => (
|
|
||||||
<DatasetCard
|
|
||||||
key={dataset.id}
|
|
||||||
dataset={dataset}
|
|
||||||
showOrg={true}
|
|
||||||
urlPrefix={urlPrefix}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{data?.count && (
|
|
||||||
<Pagination
|
|
||||||
options={options}
|
|
||||||
subsetOfPages={subsetOfPages}
|
|
||||||
setSubsetOfPages={setSubsetOfPages}
|
|
||||||
setOptions={setOptions}
|
|
||||||
count={data.count}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import { Dispatch, SetStateAction } from "react";
|
|
||||||
import { PackageSearchOptions } from "../interfaces";
|
|
||||||
|
|
||||||
export default function Pagination({
|
|
||||||
options,
|
|
||||||
setOptions,
|
|
||||||
subsetOfPages,
|
|
||||||
setSubsetOfPages,
|
|
||||||
count,
|
|
||||||
}: {
|
|
||||||
options: PackageSearchOptions;
|
|
||||||
setOptions: Dispatch<SetStateAction<PackageSearchOptions>>;
|
|
||||||
subsetOfPages: number;
|
|
||||||
setSubsetOfPages: Dispatch<SetStateAction<number>>;
|
|
||||||
count: number;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="flex gap-2 align-center">
|
|
||||||
{subsetOfPages !== 0 && (
|
|
||||||
<button
|
|
||||||
className="font-semibold flex items-center gap-2"
|
|
||||||
onClick={() => setSubsetOfPages(subsetOfPages - 5)}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
strokeWidth={1.5}
|
|
||||||
stroke="currentColor"
|
|
||||||
className="w-6 h-6"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Prev
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{Array.from(Array(Math.ceil(count / 5)).keys()).map((x) => (
|
|
||||||
<button
|
|
||||||
key={x}
|
|
||||||
className={`${
|
|
||||||
x == options.offset / 5 ? "bg-orange-500 text-white" : ""
|
|
||||||
} px-2 rounded font-semibold text-zinc-900`}
|
|
||||||
onClick={() => setOptions({ ...options, offset: x * 5 })}
|
|
||||||
style={{
|
|
||||||
display:
|
|
||||||
x >= subsetOfPages && x < subsetOfPages + 5 ? "block" : "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{x + 1}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
{subsetOfPages !== Math.ceil(count / 5) && count > 25 && (
|
|
||||||
<button
|
|
||||||
className="font-semibold flex items-center gap-2"
|
|
||||||
onClick={() => setSubsetOfPages(subsetOfPages + 5)}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
strokeWidth={1.5}
|
|
||||||
stroke="currentColor"
|
|
||||||
className="w-6 h-6"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { Resource } from "../interfaces";
|
|
||||||
|
|
||||||
export default function ResourceCard({
|
|
||||||
resource,
|
|
||||||
small,
|
|
||||||
}: {
|
|
||||||
resource?: Resource;
|
|
||||||
small?: boolean;
|
|
||||||
}) {
|
|
||||||
const resourceTextColors = {
|
|
||||||
PDF: "text-cyan-300",
|
|
||||||
CSV: "text-emerald-300",
|
|
||||||
JSON: "text-yellow-300",
|
|
||||||
XLS: "text-orange-300",
|
|
||||||
ODS: "text-amber-400",
|
|
||||||
DOC: "text-red-300",
|
|
||||||
SHP: "text-purple-400",
|
|
||||||
HTML: "text-pink-300",
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="col-span-1 md:pt-1.5 place-content-center md:place-content-start">
|
|
||||||
<div
|
|
||||||
className="bg-slate-900 rounded-lg max-w-[90px] min-w-[60px] mx-auto md:mx-0 flex place-content-center my-auto"
|
|
||||||
style={{ minHeight: small ? "60px" : "90px" }}
|
|
||||||
>
|
|
||||||
{(resource && resource.format && (
|
|
||||||
<span
|
|
||||||
className={`${
|
|
||||||
resourceTextColors[
|
|
||||||
resource.format as keyof typeof resourceTextColors
|
|
||||||
]
|
|
||||||
? resourceTextColors[
|
|
||||||
resource.format as keyof typeof resourceTextColors
|
|
||||||
]
|
|
||||||
: "text-gray-200"
|
|
||||||
} font-bold ${small ? "text-lg" : "text-2xl"} my-auto`}
|
|
||||||
>
|
|
||||||
{resource.format}
|
|
||||||
</span>
|
|
||||||
)) || (
|
|
||||||
<span className="font-bold text-2xl text-gray-200 my-auto">NONE</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import DatasetCard from "./DatasetCard";
|
|
||||||
import ListOfDatasets from "./ListOfDatasets";
|
|
||||||
import DatasetSearchForm from "./DatasetSearchForm";
|
|
||||||
import DatasetSearchFilters from "./DatasetSearchFilters";
|
|
||||||
|
|
||||||
export { DatasetCard, ListOfDatasets, DatasetSearchForm, DatasetSearchFilters };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from "./components";
|
|
||||||
export * from "./lib";
|
|
||||||
export * from "./interfaces";
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { User } from "./user.interface";
|
|
||||||
|
|
||||||
export interface Activity {
|
|
||||||
id: string;
|
|
||||||
timestamp: string;
|
|
||||||
user_id: string;
|
|
||||||
object_id?: string;
|
|
||||||
activity_type?: string;
|
|
||||||
user_data?: User;
|
|
||||||
data?: {
|
|
||||||
package?: {
|
|
||||||
title?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import { Activity } from "./activity.interface";
|
|
||||||
import { Group } from "./group.interface";
|
|
||||||
import { Organization } from "./organization.interface";
|
|
||||||
|
|
||||||
export interface Dataset {
|
|
||||||
author?: string;
|
|
||||||
author_email?: string;
|
|
||||||
creator_user_id?: string;
|
|
||||||
id: string;
|
|
||||||
isopen?: boolean;
|
|
||||||
license_id?: string;
|
|
||||||
license_title?: string;
|
|
||||||
maintainer?: string;
|
|
||||||
maintainer_email?: string;
|
|
||||||
metadata_created?: string;
|
|
||||||
metadata_modified?: string;
|
|
||||||
name: string;
|
|
||||||
notes?: string;
|
|
||||||
num_resources: number;
|
|
||||||
num_tags: number;
|
|
||||||
owner_org?: string;
|
|
||||||
private?: boolean;
|
|
||||||
state?: "active" | "inactive" | "deleted";
|
|
||||||
title?: string;
|
|
||||||
type?: "dataset";
|
|
||||||
url?: string;
|
|
||||||
version?: string;
|
|
||||||
activity_stream?: Array<Activity>;
|
|
||||||
resources: Array<Resource>;
|
|
||||||
organization?: Organization;
|
|
||||||
groups?: Array<Group>;
|
|
||||||
tags?: Array<Tag>;
|
|
||||||
total_downloads?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Resource {
|
|
||||||
cache_last_updated?: string;
|
|
||||||
cache_url?: string;
|
|
||||||
created?: string;
|
|
||||||
datastore_active?: boolean;
|
|
||||||
description?: string;
|
|
||||||
format?: string;
|
|
||||||
hash?: string;
|
|
||||||
id?: string;
|
|
||||||
last_modified?: string;
|
|
||||||
metadata_modified?: string;
|
|
||||||
mimetype?: string;
|
|
||||||
mimetype_inner?: string;
|
|
||||||
name?: string;
|
|
||||||
package_id?: string;
|
|
||||||
position?: number;
|
|
||||||
resource_type?: null;
|
|
||||||
size?: number;
|
|
||||||
state?: "active" | "inactive" | "deleted";
|
|
||||||
url?: string;
|
|
||||||
url_type?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DatasetListQueryOptions {
|
|
||||||
offset: number;
|
|
||||||
limit: number;
|
|
||||||
}
|
|
||||||
export interface PackageSearchOptions {
|
|
||||||
offset: number;
|
|
||||||
limit: number;
|
|
||||||
groups: Array<string>;
|
|
||||||
orgs: Array<string>;
|
|
||||||
tags: Array<string>;
|
|
||||||
query?: string;
|
|
||||||
resFormat?: Array<string>;
|
|
||||||
sort?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Tag {
|
|
||||||
display_name?: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
state: "active";
|
|
||||||
vocabulary_id?: string;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
export interface TableMetadata {
|
|
||||||
_id: string;
|
|
||||||
name?: string;
|
|
||||||
oid: number;
|
|
||||||
alias_of?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResourceInfo {
|
|
||||||
schema: Record<string, string | boolean | number>;
|
|
||||||
meta: Record<string, string | boolean | number>;
|
|
||||||
alias?: string;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { Activity } from "./activity.interface";
|
|
||||||
import { Dataset, Tag } from "./dataset.interface";
|
|
||||||
import { User } from "./user.interface";
|
|
||||||
|
|
||||||
export interface Group {
|
|
||||||
display_name: string;
|
|
||||||
description: string;
|
|
||||||
image_display_url: string;
|
|
||||||
package_count: number;
|
|
||||||
created: string;
|
|
||||||
name: string;
|
|
||||||
is_organization: false;
|
|
||||||
state: "active" | "deleted" | "inactive";
|
|
||||||
image_url: string;
|
|
||||||
type: "group";
|
|
||||||
title: string;
|
|
||||||
revision_id: string;
|
|
||||||
num_followers: number;
|
|
||||||
id: string;
|
|
||||||
approval_status: string;
|
|
||||||
packages?: Array<Dataset>;
|
|
||||||
activity_stream?: Array<Activity>;
|
|
||||||
tags?: Array<Tag>;
|
|
||||||
users?: Array<User>;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export * from './activity.interface'
|
|
||||||
export * from './dataset.interface'
|
|
||||||
export * from './datastore.interface'
|
|
||||||
export * from './group.interface'
|
|
||||||
export * from './organization.interface'
|
|
||||||
export * from './user.interface'
|
|
||||||
export * from './misc.interface'
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export interface FilterObj {
|
|
||||||
org?: string;
|
|
||||||
group?: string;
|
|
||||||
format?: string;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Activity } from "./activity.interface";
|
|
||||||
import { Dataset, Tag } from "./dataset.interface";
|
|
||||||
import { User } from "./user.interface";
|
|
||||||
|
|
||||||
export interface Organization {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
display_name: string;
|
|
||||||
type: string;
|
|
||||||
description?: string;
|
|
||||||
image_url?: string;
|
|
||||||
image_display_url?: string;
|
|
||||||
created?: string;
|
|
||||||
is_organization: boolean;
|
|
||||||
approval_status?: "approved";
|
|
||||||
state: "active";
|
|
||||||
packages?: Array<Dataset>;
|
|
||||||
activity_stream?: Array<Activity>;
|
|
||||||
users?: Array<User>;
|
|
||||||
tags?: Array<Tag>;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export interface User {
|
|
||||||
id?: string;
|
|
||||||
name?: string;
|
|
||||||
fullname?: string;
|
|
||||||
created?: string;
|
|
||||||
about?: null;
|
|
||||||
activity_streams_email_notifications?: boolean;
|
|
||||||
sysadmin?: boolean;
|
|
||||||
state?: "active" | "inactive" | "deleted";
|
|
||||||
image_url?: string;
|
|
||||||
display_name?: string;
|
|
||||||
email_hash?: string;
|
|
||||||
number_created_packages?: number;
|
|
||||||
apikey?: string;
|
|
||||||
email?: string;
|
|
||||||
image_display_url?: string;
|
|
||||||
}
|
|
||||||
@@ -1,389 +0,0 @@
|
|||||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
|
||||||
import { Activity } from '../interfaces/activity.interface';
|
|
||||||
import {
|
|
||||||
Dataset,
|
|
||||||
DatasetListQueryOptions,
|
|
||||||
PackageSearchOptions,
|
|
||||||
Resource,
|
|
||||||
Tag,
|
|
||||||
} from '../interfaces/dataset.interface';
|
|
||||||
import { ResourceInfo, TableMetadata } from '../interfaces/datastore.interface';
|
|
||||||
import { Group } from '../interfaces/group.interface';
|
|
||||||
import { Organization } from '../interfaces/organization.interface';
|
|
||||||
import { User } from '../interfaces/user.interface';
|
|
||||||
import fetchRetry from './utils';
|
|
||||||
|
|
||||||
export default class CKAN {
|
|
||||||
DMS: string;
|
|
||||||
|
|
||||||
constructor(DMS: string) {
|
|
||||||
this.DMS = DMS;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDatasetsList() {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${this.DMS}/api/3/action/package_list`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
return responseData.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDatasetsListWithDetails(options: DatasetListQueryOptions) {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/current_package_list_with_resources?offset=${
|
|
||||||
options.offset
|
|
||||||
}&limit=${options.limit}`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const datasets: Array<Dataset> = responseData.result;
|
|
||||||
return datasets;
|
|
||||||
}
|
|
||||||
|
|
||||||
async packageSearch(
|
|
||||||
options: PackageSearchOptions
|
|
||||||
): Promise<{ datasets: Dataset[]; count: number }> {
|
|
||||||
function buildGroupsQuery(groups: Array<string>) {
|
|
||||||
if (groups.length > 0) {
|
|
||||||
return `groups:(${groups.join(' OR ')})`;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
function buildOrgsQuery(orgs: Array<string>) {
|
|
||||||
if (orgs.length > 0) {
|
|
||||||
return `organization:(${orgs.join(' OR ')})`;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
function buildTagsQuery(tags: Array<string>) {
|
|
||||||
if (tags.length > 0) {
|
|
||||||
return `tags:(${tags.join(' OR ')})`;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildResFormatQuery(resFormat: Array<string>) {
|
|
||||||
if (resFormat?.length > 0) {
|
|
||||||
return `res_format:(${resFormat.join(' OR ')})`;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildFq(
|
|
||||||
tags: Array<string>,
|
|
||||||
orgs: Array<string>,
|
|
||||||
groups: Array<string>,
|
|
||||||
resFormat: Array<string>
|
|
||||||
) {
|
|
||||||
//TODO; this query builder is not very robust
|
|
||||||
// convertToCkanSearchQuery function should be
|
|
||||||
//copied over from the old portals utils
|
|
||||||
const fq = [
|
|
||||||
buildGroupsQuery(groups),
|
|
||||||
buildOrgsQuery(orgs),
|
|
||||||
buildTagsQuery(tags),
|
|
||||||
buildResFormatQuery(resFormat),
|
|
||||||
].filter((str) => str !== '');
|
|
||||||
if (fq.length > 0) {
|
|
||||||
return '&fq=' + fq.join('+');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fq = buildFq(
|
|
||||||
options.tags,
|
|
||||||
options.orgs,
|
|
||||||
options.groups,
|
|
||||||
options?.resFormat
|
|
||||||
);
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/package_search?start=${options.offset}&rows=${
|
|
||||||
options.limit
|
|
||||||
}${fq ? fq : ''}${options.query ? '&q=' + options.query : ''}${
|
|
||||||
options.sort ? '&sort=' + options.sort : ''
|
|
||||||
}`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const datasets: Array<Dataset> = responseData.result.results;
|
|
||||||
return { datasets, count: responseData.result.count };
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDatasetDetails(datasetName: string) {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/package_show?id=${datasetName}`,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
if (responseData.success === false) {
|
|
||||||
throw 'Could not find dataset';
|
|
||||||
}
|
|
||||||
const dataset: Dataset = responseData.result;
|
|
||||||
return dataset;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDatasetActivityStream(datasetName: string) {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/package_activity_list?id=${datasetName}`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const activitiesWithoutUserData: Array<Activity> = responseData.result;
|
|
||||||
const activities = await Promise.all(
|
|
||||||
activitiesWithoutUserData.map(async (item) => {
|
|
||||||
let user_data: User | null = await this.getUser(item.user_id);
|
|
||||||
user_data = user_data === undefined ? null : user_data;
|
|
||||||
return { ...item, user_data };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return activities;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUser(userId: string) {
|
|
||||||
try {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/user_show?id=${userId}`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const user: User | null =
|
|
||||||
responseData.success === true ? responseData.result : null;
|
|
||||||
return user;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGroupList() {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${this.DMS}/api/3/action/group_list`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const groups: Array<string> = responseData.result;
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGroupsWithDetails() {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/group_list?all_fields=True`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const groups: Array<Group> = responseData.result;
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGroupDetails(groupName: string) {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/group_show?id=${groupName}&include_datasets=True`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const group: Group = responseData.result;
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getGroupActivityStream(groupName: string) {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/group_activity_list?id=${groupName}`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const activitiesWithoutUserData: Array<Activity> = responseData.result;
|
|
||||||
const activities = await Promise.all(
|
|
||||||
activitiesWithoutUserData.map(async (item) => {
|
|
||||||
const user_data = await this.getUser(item.user_id);
|
|
||||||
return { ...item, user_data };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return activities;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOrgList() {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${this.DMS}/api/3/action/organization_list`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const organizations: Array<string> = responseData.result;
|
|
||||||
return organizations;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOrgsWithDetails(accrossPages?: boolean) {
|
|
||||||
if (!accrossPages) {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/organization_list?all_fields=True`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const organizations: Array<Organization> = responseData.result;
|
|
||||||
return organizations;
|
|
||||||
}
|
|
||||||
|
|
||||||
const organizations = [];
|
|
||||||
const orgListResponse = await fetchRetry(
|
|
||||||
`${this.DMS}/api/3/action/organization_list`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const orgList = await orgListResponse.json();
|
|
||||||
const orgLen = orgList.result.length;
|
|
||||||
const pages = Math.ceil(orgLen / 25);
|
|
||||||
|
|
||||||
for (let i = 0; i < pages; i++) {
|
|
||||||
let allOrgListResponse = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/organization_list?all_fields=True&offset=${
|
|
||||||
i * 25
|
|
||||||
}&limit=25`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await allOrgListResponse.json();
|
|
||||||
const result: Array<Organization> = responseData.result;
|
|
||||||
organizations.push(...result);
|
|
||||||
}
|
|
||||||
return organizations.sort((a, b) => b.package_count - a.package_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOrgDetails(orgName: string) {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/organization_show?id=${orgName}&include_datasets=True`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const organization: Organization = responseData.result;
|
|
||||||
return organization;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOrgActivityStream(orgName: string) {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/organization_activity_list?id=${orgName}`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const activitiesWithoutUserData: Array<Activity> = responseData.result;
|
|
||||||
const activities = await Promise.all(
|
|
||||||
activitiesWithoutUserData.map(async (item) => {
|
|
||||||
const user_data = await this.getUser(item.user_id);
|
|
||||||
return { ...item, user_data };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return activities;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllTags() {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/tag_list?all_fields=True`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const tags: Array<Tag> = responseData.result;
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getResourcesWithAliasList() {
|
|
||||||
const response = await fetch(
|
|
||||||
`${this.DMS}/api/3/action/datastore_search`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
id: '_table_metadata',
|
|
||||||
limit: '32000',
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const tableMetadata: Array<TableMetadata> = responseData.result.records;
|
|
||||||
return tableMetadata.filter((item) => item.alias_of);
|
|
||||||
}
|
|
||||||
|
|
||||||
async datastoreSearch(resourceId: string) {
|
|
||||||
const response = await fetch(
|
|
||||||
`${this.DMS}/api/3/action/datastore_search`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
id: resourceId,
|
|
||||||
limit: '32000',
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
return responseData.result.records;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getResourceMetadata(resourceId: string) {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/resource_show?id=${resourceId}`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const resourceMetadata: Resource = responseData.result;
|
|
||||||
return resourceMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getResourceInfo(resourceId: string) {
|
|
||||||
const response = await fetch(
|
|
||||||
`${this.DMS}/api/3/action/datastore_info`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ resource_id: resourceId }),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const resourceInfo: Array<ResourceInfo> = responseData.result;
|
|
||||||
return resourceInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFacetFields(field: 'res_format' | 'tags') {
|
|
||||||
const response = await fetchRetry(
|
|
||||||
`${
|
|
||||||
this.DMS
|
|
||||||
}/api/3/action/package_search?facet.field=["${field}"]&rows=0`,
|
|
||||||
3
|
|
||||||
);
|
|
||||||
const responseData = await response.json();
|
|
||||||
const result = responseData.result?.facets?.[field];
|
|
||||||
return Object.keys(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import CKAN from './ckanapi'
|
|
||||||
export { CKAN }
|
|
||||||
export * from './utils'
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
export function getDaysAgo(date: string) {
|
|
||||||
const today = new Date();
|
|
||||||
const createdOn = new Date(date);
|
|
||||||
const msInDay = 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
createdOn.setHours(0, 0, 0, 0);
|
|
||||||
today.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
return (+today - +createdOn) / msInDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function fetchRetry(url: string, n: number): Promise<any> {
|
|
||||||
const abortController = new AbortController();
|
|
||||||
const id = setTimeout(() => abortController.abort(), 30000);
|
|
||||||
const res = await fetch(url, { signal: abortController.signal });
|
|
||||||
clearTimeout(id);
|
|
||||||
if (!res.ok && n && n > 0) {
|
|
||||||
return await fetchRetry(url, n - 1);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeTag(tag?: string) {
|
|
||||||
if (tag === "{{description}}" || !tag) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
const div = document.createElement("div");
|
|
||||||
div.innerHTML = tag;
|
|
||||||
return div.textContent || div.innerText || "";
|
|
||||||
}
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
//The porpuse of this functoin is converting the schema returned from datastore info which is in the form o a dictionary into an array of key value pairs that can be consumed by the data-explorer
|
|
||||||
export function convertFieldSchema(
|
|
||||||
schema: Record<string, string | boolean | number>
|
|
||||||
) {
|
|
||||||
function convertToGraphqlString(fieldName: string) {
|
|
||||||
return fieldName
|
|
||||||
.replaceAll(" ", "_")
|
|
||||||
.replaceAll("(", "_")
|
|
||||||
.replaceAll(")", "_")
|
|
||||||
.replace(/[^\w\s]|(_)\1/gi, "_");
|
|
||||||
}
|
|
||||||
const entries = Object.entries(schema);
|
|
||||||
return {
|
|
||||||
fields: entries.map((entry) => ({
|
|
||||||
name: convertToGraphqlString(entry[0]),
|
|
||||||
title: entry[0],
|
|
||||||
type: entry[1],
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
1
packages/ckan/src/vite-env.d.ts
vendored
1
packages/ckan/src/vite-env.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
||||||
"module": "ESNext",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
// "strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"references": [{ "path": "./tsconfig.node.json" }],
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import react from '@vitejs/plugin-react';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { defineConfig } from 'vite';
|
|
||||||
import dts from 'vite-plugin-dts';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
react(),
|
|
||||||
dts({
|
|
||||||
insertTypesEntry: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
build: {
|
|
||||||
lib: {
|
|
||||||
entry: path.resolve(__dirname, 'src/index.ts'),
|
|
||||||
name: 'components',
|
|
||||||
formats: ['es', 'umd'],
|
|
||||||
fileName: (format) => `components.${format}.js`,
|
|
||||||
},
|
|
||||||
rollupOptions: {
|
|
||||||
external: ['react', 'react-dom', 'styled-components'],
|
|
||||||
output: {
|
|
||||||
globals: {
|
|
||||||
react: 'React',
|
|
||||||
'react-dom': 'ReactDOM'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user