Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new observation form for custom observations #70

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ae6a40f
Add new observation form for custom observations
Bo-Duke Apr 24, 2024
3e6d97a
Test contribution endpoint
Bo-Duke Apr 25, 2024
52e2837
Add stations details
Bo-Duke Apr 25, 2024
b7fb855
Add Observation details endpoints
Bo-Duke Apr 29, 2024
96a505e
Add observation details page
Bo-Duke Apr 29, 2024
446f236
Auto select station in observation form
Bo-Duke Apr 29, 2024
b7e7935
Add pictures support to custom observations
Bo-Duke Apr 29, 2024
fd6d756
Remove file category select
Bo-Duke Apr 29, 2024
ba5d2fb
Dont show default observation types when custom ones exist
Bo-Duke Apr 29, 2024
40f9a78
PR review
Bo-Duke Apr 29, 2024
5d3d94b
allow specific branch docker build
submarcos Apr 29, 2024
c6bc33f
Add password support for observation form
Bo-Duke May 2, 2024
7d78ddb
Add attribution to map
Bo-Duke May 2, 2024
6cd31a2
Fix grouped layers causing crash
Bo-Duke May 3, 2024
bf75392
Feat: Add button to redirect user to external page if link on content
babastienne May 3, 2024
6abcf19
Use LinkAsButton component
Bo-Duke May 3, 2024
766fa4a
Merge pull request #75 from Georiviere/handle_external_links_on_detai…
Bo-Duke May 3, 2024
577bc24
5 minutes cache instead of one hour
Bo-Duke May 3, 2024
54ecf89
Add cache revalidation endpoint
Bo-Duke May 3, 2024
aa9e077
Add bad password error on contribution form
Bo-Duke May 3, 2024
9398157
Open external link in new tab
Bo-Duke May 3, 2024
b634111
feat: add external uri button on Station
babastienne May 7, 2024
3600e80
Merge pull request #76 from Georiviere/add_link_on_stations
babastienne May 7, 2024
56962f4
Convert null value from postObservation form to empty string
dtrucs Jul 3, 2024
0d8ad1b
Bump @bhch/react-json-form from 2.13.5 to 2.14.1
dtrucs Jul 8, 2024
b548b11
Add missing keys for loops inside jsx
dtrucs Nov 12, 2024
c28293d
Call getObservations in server side
dtrucs Nov 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: Release docker image

on:
push:
branches:
- main
- new_observation_types
release:
types: [created]

Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module '@bhch/react-json-form';
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"dependencies": {
"@20tab/react-leaflet-resetview": "^1.1.0",
"@bhch/react-json-form": "^2.14.1",
"@hookform/resolvers": "^3.1.0",
"@radix-ui/react-accordion": "^1.1.1",
"@radix-ui/react-dialog": "^1.0.4",
Expand All @@ -34,10 +35,12 @@
"next": "^13.5.4",
"next-intl": "2.15.0-beta.5",
"next-themes": "^0.2.1",
"node-fetch": "^3.3.2",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useful?

"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.43.9",
"react-leaflet": "^4.2.1",
"react-modal": "^3.16.1",
"sharp": "^0.32.6",
"slugify": "^1.6.6",
"tailwind-merge": "^2.2.2",
Expand Down
126 changes: 126 additions & 0 deletions src/api/customObservations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { JSONSchema } from 'json-schema-yup-transformer/dist/schema';

import { Attachement } from './settings';

export type Observation = {
id: number;
label: string;
description: string;
json_schema_form: JSONSchema;
stations: number[];
password_required?: boolean;
};

export type ObservationDetails = {
values: { id: string; value: any; label?: string }[];
id: string;
contributedAt: string;
label?: string;
description?: string;
attachments?: Attachement[];
geometry?: {
coordinates: number[];
};
};

type ObservationListItem = {
id: number;
contributed_at: string;
attachments: Attachement[];
};

async function fetchObservations(): Promise<Observation[]> {
const res = await fetch(
`${process.env.apiHost}/api/portal/fr/${process.env.portal}/custom-contribution-types/`,
{
next: { revalidate: 5 * 60, tags: ['admin', 'contributions'] },
headers: {
Accept: 'application/json',
},
},
);
if (res.status < 200 || res.status > 299) {
return [];
}
return res.json();
}

async function fetchObservation(id: string): Promise<Observation | null> {
const res = await fetch(
`${process.env.apiHost}/api/portal/fr/${process.env.portal}/custom-contribution-types/${id}`,
{
next: { revalidate: 5 * 60, tags: ['admin', 'contributions'] },
headers: {
Accept: 'application/json',
},
},
);
if (res.status < 200 || res.status > 299) {
return null;
}
return res.json();
}

async function fetchObservationDetails(
id: string,
): Promise<ObservationListItem[] | null> {
const res = await fetch(
`${process.env.apiHost}/api/portal/fr/${process.env.portal}/custom-contribution-types/${id}/contributions`,
{
next: { revalidate: 5 * 60, tags: ['admin', 'contributions'] },
headers: {
Accept: 'application/json',
},
},
);
if (res.status < 200 || res.status > 299) {
return null;
}
return res.json();
}

export async function getObservationDetails(
type: string,
id: string,
): Promise<ObservationDetails | null> {
const schema = await fetchObservation(type);
const detailsList = await fetchObservationDetails(type);
Comment on lines +86 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can use a Promise.all to parallelize queries?

const values = detailsList?.find(detail => detail.id === parseInt(id));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const values = detailsList?.find(detail => detail.id === parseInt(id));
const values = detailsList?.find(detail => detail.id === Number(id));

or parseInt(id, 10))


if (!values) return null;

const details = {
values: Object.entries(values)
.filter(([key]) => schema?.json_schema_form.properties?.[key])
.map(([key, value]) => ({
id: key,
value,
label: (schema?.json_schema_form.properties?.[key] as any)?.title,
})),
id,
contributedAt: values?.contributed_at,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional chaining seems useless with the early return line 90

label: schema?.label,
description: schema?.description,
attachments: values?.attachments,
};

return details;
}

export async function getObservation(id: string) {
const observation = await fetchObservation(id);
return {
...observation,
json_schema_form: {
...observation?.json_schema_form,
properties: {
...observation?.json_schema_form?.properties,
},
},
};
Comment on lines +111 to +120
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's more readable to remove all optional chaining by using an early return if observation === null

}

export async function getObservations() {
const observations = await fetchObservations();
return observations;
}
6 changes: 4 additions & 2 deletions src/api/details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getDetailsUrl } from './settings';

async function fetchDetails(url: string) {
const res = await fetch(`${process.env.apiHost}${url}`, {
next: { revalidate: 60 * 60 },
next: { revalidate: 5 * 60, tags: ['details', 'admin'] },
headers: {
Accept: 'application/json',
},
Expand All @@ -26,7 +26,9 @@ export async function getDetails(path: string, id: number) {
}
let details = null;
try {
details = await fetchDetails(`${endpoint}${id}`);
details = await fetchDetails(
`/api/portal/fr/${process.env.portal}/${path}/${id}`,
);
} catch (e) {
// notfound
}
Expand Down
2 changes: 1 addition & 1 deletion src/api/geojson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { GeoJSON } from 'geojson';

async function fetchGeoJSON(url: string) {
const res = await fetch(`${process.env.apiHost}${url}`, {
next: { revalidate: 60 * 60 },
next: { revalidate: 20 * 60, tags: ['admin', 'geojson'] },
});
if (res.status < 200 || res.status > 299) {
return null;
Expand Down
15 changes: 5 additions & 10 deletions src/api/observations.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import {
JSONSchema,
JSONSchemaType,
} from 'json-schema-yup-transformer/dist/schema';

import { getCorrespondingPath } from '@/lib/utils';

export type PostObservationProps = {
Expand All @@ -15,7 +10,7 @@ async function fetchObservation() {
const res = await fetch(
`${process.env.apiHost}/api/portal/fr/${process.env.portal}/contributions/json_schema/`,
{
next: { revalidate: 60 * 60 },
next: { revalidate: 5 * 60, tags: ['admin', 'observations'] },
headers: {
Accept: 'application/json',
},
Expand Down Expand Up @@ -79,10 +74,10 @@ async function postObservation(props: PostObservationProps) {
}
}

function observationAdapter(json: JSONSchema, path: string) {
function observationAdapter(json: any, path: string) {
const { category, ...jsonProperties } = json.properties;
const { then: type } = json.allOf.find(
(item: JSONSchemaType) =>
const { then: type } = json.allOf?.find(
(item: any) =>
item.if.properties.category?.const === getCorrespondingPath(path),
);
return {
Expand All @@ -103,7 +98,7 @@ function observationAdapter(json: JSONSchema, path: string) {
...jsonProperties,
...type.properties,
},
required: [...json.required, 'lng', 'lat'].filter(
required: [...(json.required ?? []), 'lng', 'lat'].filter(
item => item !== 'category',
),
};
Expand Down
2 changes: 1 addition & 1 deletion src/api/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Menu, getMenuSettings } from './settings';

async function fetchDetails(url: string) {
const res = await fetch(`${process.env.apiHost}${url}`, {
next: { revalidate: 60 * 60 },
next: { revalidate: 5 * 60, tags: ['admin', 'staticpages'] },
headers: {
Accept: 'application/json',
},
Expand Down
2 changes: 1 addition & 1 deletion src/api/poi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async function fetchPois({ params }: FetchPoisProps = {}): Promise<Poi[]> {
headers: {
Accept: 'application/json',
},
next: { revalidate: 60 * 60 },
next: { revalidate: 5 * 60, tags: ['admin', 'pois'] },
});
if (res.status < 200 || res.status > 299) {
return [];
Expand Down
56 changes: 56 additions & 0 deletions src/api/postObservation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
async function postObservation(
props: { [key: string]: string | Blob },
id: string,
formData: FormData,
) {
const body = new FormData();

Object.entries(props).forEach(([key, value]) => {
// `null` in formData is converted to `'null'`
// We convert them to an empty string to avoid it
const nextValue = value !== null ? value : '';
body.append(key, nextValue);
});

Array.from({ length: 5 }).forEach((_, index) => {
const number = index + 1;
const file = formData.get(`file${number}-file`) as File;
if (file && file.size > 0) {
body.append(`file${number}`, file);
}
});

try {
const res = await fetch(
`${process.env.apiHost}/api/portal/fr/${process.env.portal}/custom-contribution-types/${id}/contributions/`,
{
method: 'POST',
body,
},
).catch(errorServer => {
throw Error(errorServer);
});
if (res.status > 499) {
throw Error(res.statusText);
}
const json = await res.json();

if (res.status < 200 || res.status > 299) {
return { error: true, message: json };
}
return { error: false, message: json };
} catch (error) {
let message = 'Unknown Error';
if (error instanceof Error) message = error.message;
return { error: true, message };
}
}

export async function handleSubmitCustomObservation(
body: { [key: string]: string | Blob },
id: string,
formData: FormData,
) {
'use server';
return await postObservation(body, id, formData);
}
6 changes: 4 additions & 2 deletions src/api/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ type BaseLayers = {
id: number;
label: string;
url: string;
attribution: string;
control: {
attribution: string;
};
}[];

export type RawLayer = {
Expand Down Expand Up @@ -156,7 +158,7 @@ export async function fetchSettings(): Promise<RawSettings> {
const res = await fetch(
`${process.env.apiHost}/api/portal/fr/portal/${process.env.portal}/`,
{
next: { revalidate: 60 * 60 },
next: { revalidate: 5 * 60, tags: ['admin', 'settings'] },
headers: {
Accept: 'application/json',
},
Expand Down
Loading