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

feat(#1): catalogue and recommender integration #37

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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: 3 additions & 1 deletion .example.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# App env
export BATCH_SIZE=40
export BATCH_SIZE=10
export CATALOGUE_URL="http://localhost:3030"
export RECOMMENDER_URL="http://localhost:8000"
# Docker
export MARKETPLACE_IMAGETAG="dev"
# Kubernetes
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/build_push_docker_image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ jobs:
run: |
standard

- name: Create environment file
run: |
echo "CATALOGUE_URL=${{ secrets.CATALOGUE_URL }}" > .env.production
echo "RECOMMENDER_URL=${{ secrets.RECOMMENDER_URL }}" >> .env.production
AlvaroFernandezBejarano marked this conversation as resolved.
Show resolved Hide resolved

- name: Kaniko build & push
uses: aevea/action-kaniko@master
with:
Expand Down
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
FROM node:20-alpine AS deps

ARG BUILD_CMD="build"

WORKDIR /app
COPY package.json package-lock.json ./
RUN apk add --no-cache bind-tools libc6-compat && \
Expand All @@ -10,7 +12,7 @@ FROM node:20-alpine as builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
RUN npm run ${BUILD_CMD}
AlvaroFernandezBejarano marked this conversation as resolved.
Show resolved Hide resolved

FROM node:18-alpine as runner
WORKDIR /app
Expand Down
4 changes: 1 addition & 3 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ const nextConfig = {
remotePatterns: [
{
protocol: 'https',
hostname: 'picsum.photos',
port: '',
pathname: '/200'
hostname: '**'
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not ideal in terms of security, but we have no choice if we want to be able to render any images stored in the offerings and participants.. if you have any ideas to make this more secure, let me know!

Copy link
Member

Choose a reason for hiding this comment

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

Nothing comes to mind as we are not the ones managing/storing the images...

}
]
}
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"dev": "next dev",
"fast": "next dev --turbo",
"build": "next build",
"compile": "next build --experimental-build-mode compile",
"generate": "next build --experimental-build-mode generate",
Comment on lines +9 to +10
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This two commands are here to enable us to build the app without prefetching, therefore not requiring valid URLs for the catalogue or recommender. I am happy to remove that if you find this irrelevant.

Choose a reason for hiding this comment

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

Hmmm I am more curious on why did you add it, as you add to the workflow the env? And ususally we don't need to build it as we work with it on dev mode (Just curious)

"start": "next start",
"lint": "next lint"
},
Expand Down
Binary file removed public/img/mock_asset_img.jpg
Binary file not shown.
Binary file removed public/img/mock_provider.png
Binary file not shown.
Binary file added public/img/ukri-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ import { Badge } from 'flowbite-react'
import Image from 'next/image'
import { HiLocationMarker, HiCalendar, HiOutlineRefresh } from 'react-icons/hi'
import Credentials from './Credentials'
import ProviderCard from './ProviderCard'
import mockProvider from '@/utils/data/mockProvider.json'
import settings from '@/utils/settings'

function Asset ({ asset }) {
const title = asset.title
const imageUrl = asset.picture
const shortDescription = asset.short_description
const keywords = asset.keywords
const createdAt = new Date(asset.created_at)
const updatedAt = new Date(asset.updated_at)
const location = asset.location
const description = asset.description
function Asset ({ offering }) {
const title = offering.title.value
const imageUrl = offering?.picture?.value ?? 'https://picsum.photos/200'
const shortDescription = offering.description.value
const keywords = offering?.keywords?.value ? offering.keywords.value.split(settings.keywordsSeparator) : []
const createdAt = new Date(offering.created.value)
const updatedAt = new Date(offering?.updated?.value ?? offering.created.value)
const location = offering?.location?.value ?? 'London'
const description = offering.description.value
return (
<div className='flex flex-col bg-sedimark-light-blue'>
<h5 className='text-3xl font-bold tracking-tight text-black dark:text-white'>
Expand Down Expand Up @@ -60,8 +59,7 @@ function Asset ({ asset }) {
})}
</div>
</div>
<Credentials asset={asset} />
<ProviderCard provider={mockProvider} />
<Credentials offering={offering} />
</div>
)
}
Expand Down
24 changes: 24 additions & 0 deletions src/app/catalogue/[offeringId]/components/BackToSearchButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client'
import { Button } from 'flowbite-react'
import { HiArrowLeft } from 'react-icons/hi'

export default function BackToSearchButton () {
const goBack = () => {
if (document.referrer.includes('/catalogue?')) {
window.history.back()
} else {
window.location.href = '/catalogue'
}
}
AlvaroFernandezBejarano marked this conversation as resolved.
Show resolved Hide resolved
return (
<Button
className='max-w-fit m-2 ml-10 bg-sedimark-light-gray enabled:hover:bg-sedimark-medium-gray text-sedimark-dark-gray'
onClick={goBack}
>
<span className='flex items-center'>
<HiArrowLeft className='mr-2' />
Back to search
</span>
</Button>
)
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import Asset from './Asset'
import ProviderCard from './ProviderCard'
import PriceCard from './PriceCard'
import mockAsset from '@/utils/data/mockAsset.json'

function DatasetInfoComponent () {
const asset = mockAsset
function DatasetInfoComponent ({ offering, provider }) {
return (
<>
<div className='flex justify-between m-10 bg-sedimark-light-blue'>
<div className='w-2/3 pt-1'>
<Asset asset={asset} />
<Asset offering={offering} />
<ProviderCard provider={provider} />
</div>
<div className='pt-2'>
<PriceCard price={asset.price} />
<PriceCard price={offering?.price ?? 0} />
AlvaroFernandezBejarano marked this conversation as resolved.
Show resolved Hide resolved
</div>
</div>
</>
Expand Down
37 changes: 37 additions & 0 deletions src/app/catalogue/[offeringId]/components/ProviderCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Image from 'next/image'
import { HiMail, HiGlobeAlt, HiUser } from 'react-icons/hi'

function ProviderCard ({ provider }) {
return (
<>
<h5 className='text-xl font-bold'>Provided by</h5>
<div className='flex flex-wrap m-5 ml-0'>
<div className='float-left mr-5'>
{provider?.image &&
<Image width={96} height={96} src={provider.image.value} alt={provider.participant.value} className='object-cover object-center rounded-lg shadow-lg' />}
</div>
<div className='flex flex-col justify-around'>
<p className='text-md text-black font-bold'>
{provider.accountId.value + ' ( ' + provider.participant.value + ' )'}
</p>
{provider?.givenName && provider?.familyName &&
<div className='flex items-center gap-2'>
<HiUser size={20} />
<p>{provider.givenName.value + ' ' + provider.familyName.value}</p>
</div>}
{provider?.email &&
<div className='flex items-center gap-2'>
<HiMail size={20} />
<a href={provider.email.value}>{provider.email.value.split(':')[1]}</a>
</div>}
{provider?.homepage &&
<div className='flex items-center gap-2'>
<HiGlobeAlt size={20} />
<a href={provider.homepage.value} target='_blank' rel='noreferrer'>{provider.homepage.value}</a>
</div>}
</div>
</div>
</>
)
}
export default ProviderCard
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ function Recommender ({ recommendations }) {
<div className='flex gap-4 overflow-x-auto p-4 w-full'>
{recommendations.map((recommendation, index) => {
return (
<Link key={`${recommendation.title}-${recommendation.created_at}-${index + 1}`} href='#'>
<Link key={`${recommendation.title.value}-${recommendation.created.value}-${index + 1}`} href={`/catalogue/${btoa(recommendation.offering.value)}`}>
AlvaroFernandezBejarano marked this conversation as resolved.
Show resolved Hide resolved
<RecommenderItem
vc={recommendation}
providerName={recommendation.provider}
price={recommendation.price}
providerName={recommendation.publisher.value}
price={recommendation?.price ?? 0}
color='bg-sedimark-light-blue bg-red'
/>
</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { HiOutlineCurrencyEuro, HiCalendar, HiUser } from 'react-icons/hi'
function RecommenderItem ({ vc, providerName, price, color }) {
const maxLengthTitle = 42
const maxLengthDescription = 60
const name = vc.title.length > maxLengthTitle ? vc.title.substring(0, maxLengthTitle) + '...' : vc.title
const description = vc.short_description.length > maxLengthDescription ? vc.short_description.substring(0, maxLengthDescription) + '...' : vc.short_description
const issuanceDate = vc.created_at ? new Date(vc.created_at) : new Date()
const name = vc.title.value.length > maxLengthTitle ? vc.title.value.substring(0, maxLengthTitle) + '...' : vc.title.value
const description = vc.description.value.length > maxLengthDescription ? vc.description.value.substring(0, maxLengthDescription) + '...' : vc.description.value
const issuanceDate = vc.created.value ? new Date(vc.created.value) : new Date()
const date = isNaN(issuanceDate.getTime()) ? new Date() : issuanceDate
const providedBy = providerName ?? 'OTHER'
const validatedPrice = price ?? '0'
Expand Down
22 changes: 22 additions & 0 deletions src/app/catalogue/[offeringId]/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import BackToSearchButton from './components/BackToSearchButton'
import DatasetInfoComponent from './components/DatasetInfoComponent'
import Recommender from './components/Recommender'
import { fetchOfferingDetails, fetchProvider } from '@/utils/catalogue'
import { fetchSimilarRecommendations } from '@/utils/recommender'

export default async function Page ({ params }) {
const { offeringId } = params
const offeringIdDecoded = atob(decodeURIComponent(offeringId))
const offering = await fetchOfferingDetails(offeringIdDecoded)
const provider = await fetchProvider(offering.publisher.value)
const recommendations = await fetchSimilarRecommendations(offeringIdDecoded, 5)
return (
<>
<div className='bg-sedimark-light-blue pt-2 pb-2'>
<BackToSearchButton />
<DatasetInfoComponent offering={offering} provider={provider} />
<Recommender recommendations={recommendations} />
</div>
</>
)
}
27 changes: 0 additions & 27 deletions src/app/catalogue/asset/components/ProviderCard.jsx

This file was deleted.

22 changes: 0 additions & 22 deletions src/app/catalogue/asset/page.js

This file was deleted.

15 changes: 10 additions & 5 deletions src/app/catalogue/components/Catalogue.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Suspense } from 'react'
import ResultsPane from './ResultsPane'
import mockProviders from '@/utils/data/mockProviders.json'
import mockFilters from '@/utils/data/mockFilters.json'
import { fetchOfferings, fetchProviders, fetchKeywords } from '@/utils/catalogue'
import { fetchQueryRecommendations } from '@/utils/recommender'
import Recommender from '../[offeringId]/components/Recommender'

/**
* Renders the Catalogue component.
Expand All @@ -12,16 +13,20 @@ import mockFilters from '@/utils/data/mockFilters.json'
*
* @returns {JSX.Element} The Catalogue component.
*/
export default function Catalogue () {
const providers = mockProviders
const keywords = mockFilters
export default async function Catalogue ({ query, currentPage }) {
const providers = await fetchProviders(query)
const keywords = await fetchKeywords(query)
const data = await fetchOfferings(query, currentPage)
const recommendations = await fetchQueryRecommendations(query, 5)

return (
<Suspense>
<ResultsPane
providers={providers}
keywords={keywords}
data={data}
/>
<Recommender recommendations={recommendations} />
</Suspense>
)
}
17 changes: 8 additions & 9 deletions src/app/catalogue/components/OfferingItem.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import { HiOutlineCurrencyEuro, HiCalendar, HiUser } from 'react-icons/hi'

function OfferingItem ({ vc, providerName, price, color }) {
const name = vc.title
const description = vc.short_description
const issuanceDate = vc.created_at ? new Date(vc.created_at) : new Date()
function OfferingItem ({ offering, color }) {
const name = offering.title.value
const description = offering.description.value
const issuanceDate = offering.created.value ? new Date(offering.created.value) : new Date()
const date = isNaN(issuanceDate.getTime()) ? new Date() : issuanceDate
const providedBy = providerName ?? 'OTHER'
const validatedPrice = price ?? '0'
const price = offering?.price?.value ?? 0
return (
<div className={`flex flex-col p-4 rounded-lg shadow-lg ${color} hover:bg-gray-100`}>
<div className='text-lg font-semibold'>{name}</div>
<div className='flex items-center justify-between w-full'>
<div>{description}</div>
<div>{description.length > 120 ? description.substring(0, 120) + '...' : description}</div>
<div className='flex flex-row items-center gap-2 w-36'>
<HiOutlineCurrencyEuro size={20} />
<p>{validatedPrice} euros</p>
<p>{price} euros</p>
</div>
</div>
<div className='flex justify-between'>
<div className='flex flex-row gap-4'>
<div className='flex flex-row items-center gap-2'>
<HiUser size={20} />
<p className='pr-2'>{providedBy}</p>
<p className='pr-2'>{offering.publisher.value}</p>
</div>
</div>
<div className='flex flex-row items-center gap-2 w-36'>
Expand Down
8 changes: 3 additions & 5 deletions src/app/catalogue/components/ResultsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ import OfferingItem from './OfferingItem'
export default function ResultsList ({ results }) {
return (
<div className='flex flex-col w-full gap-4 p-4 bg-gray-50'>
{results.map((vc, index) => {
{results.map((offering, index) => {
return (
<Link key={`${vc.title}-${vc.created_at}-${index + 1}`} href='catalogue/asset'>
<Link key={`${offering.offering.value}-${offering.created.value}-${index + 1}`} href={`catalogue/${btoa(offering.offering.value)}`}>
<OfferingItem
vc={vc}
providerName={vc.provider}
price={vc.price}
offering={offering}
color='bg-gray-50'
/>
</Link>
Expand Down
Loading