Skip to content

Commit

Permalink
EIC Features > main (#752)
Browse files Browse the repository at this point in the history
Background for this pr: As new A&E launch being delayed, it will be
better for all the changes to stay on the same branch, and release the
version properly so we don't have to manage multiple feature branches at
the same time.

The change includes

- Story to be an external link :
#735
- Multiple stac endpoint from layer configuration:
#744
- Iframe improvement: #743,
#749 Caveat:
#749 (comment)
- Content Override for stories hub:
#736
  • Loading branch information
hanbyul-here authored Dec 5, 2023
2 parents 44f6345 + e838378 commit afe7133
Show file tree
Hide file tree
Showing 35 changed files with 799 additions and 607 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import axios from 'axios';
import { useQuery } from '@tanstack/react-query';
import booleanIntersects from '@turf/boolean-intersects';
import bboxPolygon from '@turf/bbox-polygon';
import {
areIntervalsOverlapping
} from 'date-fns';
import { areIntervalsOverlapping } from 'date-fns';
import { DatasetLayer } from 'veda';

import { MAX_QUERY_NUM } from '../constants';
Expand All @@ -25,7 +23,7 @@ interface UseStacSearchProps {
export type DatasetWithTimeseriesData = TimeseriesDataResult &
DatasetLayer & { numberOfItems: number };

const collectionUrl = `${process.env.API_STAC_ENDPOINT}/collections`;
const collectionEndpointSuffix = '/collections';

export function useStacCollectionSearch({
start,
Expand All @@ -37,10 +35,31 @@ export function useStacCollectionSearch({
const result = useQuery({
queryKey: ['stacCollection'],
queryFn: async ({ signal }) => {
const collectionResponse = await axios.get(collectionUrl, {
signal
});
return collectionResponse.data.collections;
const collectionUrlsFromDataSets = allAvailableDatasetsLayers.reduce(
(filtered, { stacApiEndpoint }) => {
return stacApiEndpoint
? [...filtered, `${stacApiEndpoint}${collectionEndpointSuffix}`]
: filtered;
},
[]
);
// Get unique values of stac api endpoints from layer data, concat with api_stac_endpoiint from env var
const collectionUrls = Array.from(
new Set(collectionUrlsFromDataSets)
).concat(`${process.env.API_STAC_ENDPOINT}${collectionEndpointSuffix}`);

const collectionRequests = collectionUrls.map((url: string) =>
axios.get(url, { signal }).then((response) => {
return response.data.collections.map((col) => ({
...col,
stacApiEndpoint: url.replace(collectionEndpointSuffix, '')
}));
})
);
return axios.all(collectionRequests).then(
// Merge all responses into one array
axios.spread((...responses) => [].concat(...responses))
);
},
enabled: readyToLoadDatasets
});
Expand Down Expand Up @@ -86,13 +105,15 @@ export function useStacCollectionSearch({

function getInTemporalAndSpatialExtent(collectionData, aoi, timeRange) {
const matchingCollectionIds = collectionData.reduce((acc, col) => {
const id = col.id;
const { id, stacApiEndpoint } = col;

// Is is a dataset defined in the app?
// If not, skip other calculations.
const isAppDataset = allAvailableDatasetsLayers.some(
(l) => l.stacCol === id
);
const isAppDataset = allAvailableDatasetsLayers.some((l) => {
const stacApiEndpointUsed =
l.stacApiEndpoint ?? process.env.API_STAC_ENDPOINT;
return l.stacCol === id && stacApiEndpointUsed === stacApiEndpoint;
});

if (
!isAppDataset ||
Expand Down Expand Up @@ -130,7 +151,11 @@ function getInTemporalAndSpatialExtent(collectionData, aoi, timeRange) {
);

const filteredDatasetsWithCollections = filteredDatasets.map((l) => {
const collection = collectionData.find((c) => c.id === l.stacCol);
const stacApiEndpointUsed =
l.stacApiEndpoint ?? process.env.API_STAC_ENDPOINT;
const collection = collectionData.find(
(c) => c.id === l.stacCol && stacApiEndpointUsed === c.stacApiEndpoint
);
return {
...l,
isPeriodic: collection['dashboard:is_periodic'],
Expand Down
23 changes: 18 additions & 5 deletions app/scripts/components/analysis/results/timeseries-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,24 +137,33 @@ export function requestStacDatasetsTimeseries({

interface DatasetAssetsRequestParams {
stacCol: string;
stacApiEndpoint?: string;
assets: string;
dateStart: Date;
dateEnd: Date;
aoi: FeatureCollection<Polygon>;
}

async function getDatasetAssets(
{ dateStart, dateEnd, stacCol, assets, aoi }: DatasetAssetsRequestParams,
{
dateStart,
dateEnd,
stacApiEndpoint,
stacCol,
assets,
aoi
}: DatasetAssetsRequestParams,
opts: AxiosRequestConfig,
concurrencyManager: ConcurrencyManagerInstance
) {
const stacApiEndpointToUse = stacApiEndpoint ?? process.env.API_STAC_ENDPOINT;
const data = await concurrencyManager.queue(async () => {
const collectionReqRes = await axios.get(
`${process.env.API_STAC_ENDPOINT}/collections/${stacCol}`
`${stacApiEndpointToUse}/collections/${stacCol}`
);

const searchReqRes = await axios.post(
`${process.env.API_STAC_ENDPOINT}/search`,
`${stacApiEndpointToUse}/search`,
{
'filter-lang': 'cql2-json',
limit: 10000,
Expand Down Expand Up @@ -238,6 +247,7 @@ async function requestTimeseries({
getDatasetAssets(
{
stacCol: layer.stacCol,
stacApiEndpoint: layer.stacApiEndpoint,
assets: layer.sourceParams?.assets || 'cog_default',
aoi,
dateStart: start,
Expand Down Expand Up @@ -267,7 +277,10 @@ async function requestTimeseries({
}
});

const analysisParams = layersBase.layer.analysis?.sourceParams;
const tileEndpointToUse =
layer.tileApiEndpoint ?? process.env.API_RASTER_ENDPOINT;

const analysisParams = layersBase.layer.analysis?.sourceParams ?? {};

const layerStatistics = await Promise.all(
assets.map(async ({ date, url }) => {
Expand All @@ -276,7 +289,7 @@ async function requestTimeseries({
async ({ signal }) => {
return concurrencyManager.queue(async () => {
const { data } = await axios.post(
`${process.env.API_RASTER_ENDPOINT}/cog/statistics`,
`${tileEndpointToUse}/cog/statistics?url=${url}`,
// Making a request with a FC causes a 500 (as of 2023/01/20)
combineFeatureCollection(aoi),
{ params: { ...analysisParams, url }, signal }
Expand Down
14 changes: 10 additions & 4 deletions app/scripts/components/common/blocks/embed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import BrowserFrame from '$styles/browser-frame';

const EmbedWrapper = styled.div`
width: 100%;
> div {
width: 100%;
}
Expand All @@ -18,19 +18,25 @@ const IframeWrapper = styled.iframe`
`;

interface EmbedProps {
className?: string;
src: string;
height: number;
}

export default function Embed({ src, height = 800 }: EmbedProps) {
export default function Embed({
className,
src,
height = 800,
...props
}: EmbedProps) {
if (!src) {
throw new HintedError('Embed block requires a URL');
}

return (
<EmbedWrapper>
<EmbedWrapper className={className}>
<BrowserFrame link={src}>
<IframeWrapper src={src} height={height} />
<IframeWrapper loading='lazy' src={src} height={height} {...props} />
</BrowserFrame>
</EmbedWrapper>
);
Expand Down
21 changes: 20 additions & 1 deletion app/scripts/components/common/blocks/lazy-components.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import T from 'prop-types';
import LazyLoad from 'react-lazyload';

import Chart from '$components/common/chart/block';
Expand All @@ -8,7 +9,7 @@ import Table, { tableHeight } from '$components/common/table';
import CompareImage from '$components/common/blocks/images/compare';

import Map, { mapHeight } from '$components/common/blocks/block-map';

import Embed from '$components/common/blocks/embed';
import {
ScrollytellingBlock,
scrollyMapHeight
Expand Down Expand Up @@ -72,3 +73,21 @@ export function LazyTable(props) {
</LazyLoad>
);
}

export function LazyEmbed(props) {
return (
<LazyLoad
// eslint-disable-next-line react/prop-types
placeholder={<LoadingSkeleton height={props.height} />}
offset={50}
once
>
<Embed {...props} />
</LazyLoad>
);
}

LazyEmbed.propTypes = {
src: T.string,
height: T.number
};
2 changes: 2 additions & 0 deletions app/scripts/components/common/blocks/scrollytelling/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,8 @@ function Scrollytelling(props) {
key={runtimeData.id}
id={runtimeData.id}
mapInstance={mapRef.current}
stacApiEndpoint={layer.stacApiEndpoint}
tileApiEndpoint={layer.tileApiEndpoint}
stacCol={layer.stacCol}
date={runtimeData.datetime}
sourceParams={layer.sourceParams}
Expand Down
50 changes: 43 additions & 7 deletions app/scripts/components/common/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { MouseEventHandler, ReactNode } from 'react';
import styled, { css } from 'styled-components';
import { Link } from 'react-router-dom';
import { format } from 'date-fns';
import { CollecticonExpandTopRight } from '@devseed-ui/collecticons';
import { VerticalDivider } from '@devseed-ui/toolbar';

import {
Expand Down Expand Up @@ -198,11 +199,11 @@ export const CardMeta = styled.div`
}
}
> ${/* sc-selector */VerticalDivider}:last-child {
> ${/* sc-selector */ VerticalDivider}:last-child {
display: none;
}
> ${/* sc-selector */VerticalDivider}:first-child {
> ${/* sc-selector */ VerticalDivider}:first-child {
display: none;
}
`;
Expand Down Expand Up @@ -284,6 +285,38 @@ const CardFigure = styled(Figure)`
}
`;

const ExternalLinkMark = styled.div`
display: flex;
align-items: center;
position: absolute;
top: ${variableGlsp(0.25)};
right: ${variableGlsp(0.25)};
padding: ${variableGlsp(0.125)} ${variableGlsp(0.25)};
background-color: ${themeVal('color.primary')};
color: ${themeVal('color.surface')};
text-transform: none;
border-radius: calc(
${multiply(themeVal('shape.rounded'), 2)} - ${variableGlsp(0.125)}
);
z-index: 1;
`;

const FlagText = styled.div`
display: inline;
font-weight: bold;
font-size: 0.825rem;
margin-right: ${variableGlsp(0.25)};
`;

export function ExternalLinkFlag() {
return (
<ExternalLinkMark>
<FlagText>External Link</FlagText>
<CollecticonExpandTopRight size='small' meaningful={false} />
</ExternalLinkMark>
);
}

interface CardComponentProps {
title: ReactNode;
linkLabel: string;
Expand Down Expand Up @@ -319,23 +352,26 @@ function CardComponent(props: CardComponentProps) {
onCardClickCapture
} = props;

const isExternalLink = linkTo.match(/^https?:\/\//);
const linkProps = isExternalLink
? { href: linkTo }
: { as: Link, to: linkTo };

return (
<ElementInteractive
as={CardSelf}
cardType={cardType}
className={className}
linkLabel={linkLabel || 'View more'}
linkProps={{
as: Link,
to: linkTo
}}
linkProps={linkProps}
onClickCapture={onCardClickCapture}
>
<CardHeader>
<CardHeadline>
<CardTitle>{title}</CardTitle>
<CardOverline as='div'>
{parentName && parentTo && (
{isExternalLink && <ExternalLinkFlag />}
{!isExternalLink && parentName && parentTo && (
<CardLabel as={Link} to={parentTo}>
{parentName}
</CardLabel>
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/common/featured-slider-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function FeaturedSliderSection(props: FeaturedSliderSectionProps) {
}}
cardType='featured'
linkLabel='View more'
linkTo={getItemPath(d)}
linkTo={d.asLink?.url ?? getItemPath(d)}
title={d.name}
overline={
<CardMeta>
Expand Down
4 changes: 2 additions & 2 deletions app/scripts/components/common/layout-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function LayoutRoot(props: { children?: ReactNode }) {

useGoogleTagManager();

const { title, thumbnail, description, hideFooter, localNavProps } =
const { title, thumbnail, description, hideFooter } =
useContext(LayoutRootContext);

const truncatedTitle =
Expand All @@ -57,7 +57,7 @@ function LayoutRoot(props: { children?: ReactNode }) {
description={description || appDescription}
thumbnail={thumbnail}
/>
<NavWrapper localNavProps={localNavProps} />
<NavWrapper />
<PageBody>
<Outlet />
{children}
Expand Down
7 changes: 6 additions & 1 deletion app/scripts/components/common/mapbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ function MapboxMapComponent(

return [data, getLayerComponent(!!data.timeseries, data.type)];
}, [compareLayer, resolverBag]);

// Get the compare to date.
// The compare date is specified by the user.
// If no date is specified anywhere we just use the same.
Expand Down Expand Up @@ -416,6 +416,8 @@ function MapboxMapComponent(
BaseLayerComponent && (
<BaseLayerComponent
id={`base-${baseLayerResolvedData.id}`}
stacApiEndpoint={baseLayerResolvedData.stacApiEndpoint}
tileApiEndpoint={baseLayerResolvedData.tileApiEndpoint}
stacCol={baseLayerResolvedData.stacCol}
mapInstance={mapRef.current}
isPositionSet={!!initialPosition}
Expand Down Expand Up @@ -470,12 +472,15 @@ function MapboxMapComponent(
<CompareLayerComponent
id={`compare-${compareLayerResolvedData.id}`}
stacCol={compareLayerResolvedData.stacCol}
stacApiEndpoint={compareLayerResolvedData.stacApiEndpoint}
tileApiEndpoint={compareLayerResolvedData.tileApiEndpoint}
mapInstance={mapCompareRef.current}
date={compareToDate ?? undefined}
sourceParams={compareLayerResolvedData.sourceParams}
zoomExtent={compareLayerResolvedData.zoomExtent}
bounds={compareLayerResolvedData.bounds}
onStatusChange={onCompareLayerStatusChange}
idSuffix='compare-suffix'
/>
)}
<SimpleMap
Expand Down
Loading

0 comments on commit afe7133

Please sign in to comment.