diff --git a/.github/workflows/analytics-api-ci.yml b/.github/workflows/analytics-api-ci.yml index 7a2fce0ad..ce8921ed0 100644 --- a/.github/workflows/analytics-api-ci.yml +++ b/.github/workflows/analytics-api-ci.yml @@ -49,6 +49,11 @@ jobs: make flake8 testing: needs: setup-job + + strategy: + matrix: + python-version: [3.12] + env: FLASK_ENV: "testing" DATABASE_TEST_URL: "postgresql://postgres:postgres@localhost:5432/postgres" @@ -69,7 +74,7 @@ jobs: USE_TEST_KEYCLOAK_DOCKER: "YES" SQLALCHEMY_DATABASE_URI: "postgresql://postgres:postgres@localhost:5432/postgres" - + runs-on: ubuntu-20.04 services: @@ -103,14 +108,14 @@ jobs: run: | echo "CODECOV_BRANCH=PR_${{github.head_ref}}" >> $GITHUB_ENV if: github.event_name == 'pull_request' - + - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: flags: analyticsapi name: codecov-analytics-api - fail_ci_if_error: true - verbose: true + fail_ci_if_error: true + verbose: true override_branch: ${{env.CODECOV_BRANCH}} token: ${{ secrets.CODECOV_TOKEN }} diff --git a/analytics-api/requirements.txt b/analytics-api/requirements.txt index 9e595dd22..f8dd6b089 100644 --- a/analytics-api/requirements.txt +++ b/analytics-api/requirements.txt @@ -11,7 +11,7 @@ Mako==1.3.3 MarkupSafe==2.1.2 SQLAlchemy-Utils==0.40.0 SQLAlchemy==1.4.52 -Werkzeug==2.3.8 +Werkzeug==3.0.3 alembic==1.13.1 aniso8601==9.0.1 attrs==23.2.0 @@ -22,7 +22,7 @@ certifi==2023.7.22 charset-normalizer==3.1.0 click==8.1.7 ecdsa==0.18.0 -flask-jwt-oidc==0.3.0 +flask_jwt_oidc==0.3.0 flask-marshmallow==1.0.0 flask-restx==1.1.0 gunicorn==20.1.0 diff --git a/analytics-api/requirements/prod.txt b/analytics-api/requirements/prod.txt index 7eb1016d3..16eb2b359 100644 --- a/analytics-api/requirements/prod.txt +++ b/analytics-api/requirements/prod.txt @@ -19,7 +19,7 @@ itsdangerous sentry-sdk[flask] bcrypt jaeger-client -Werkzeug<3 +Werkzeug<4 minio pyhumps sqlalchemy-utils diff --git a/met-web/src/components/engagement/new/view/SuggestedEngagements.tsx b/met-web/src/components/engagement/new/view/SuggestedEngagements.tsx new file mode 100644 index 000000000..9ec720cd4 --- /dev/null +++ b/met-web/src/components/engagement/new/view/SuggestedEngagements.tsx @@ -0,0 +1,150 @@ +import React, { useEffect } from 'react'; +import { Box, Grid, MobileStepper, ThemeProvider } from '@mui/material'; +import { TileSkeleton } from 'components/landing/TileSkeleton'; +import { getEngagements } from 'services/engagementService'; +import { Engagement } from 'models/engagement'; +import EngagementTile from 'components/landing/EngagementTile'; +import { RepeatedGrid } from 'components/common'; +import { Header2 } from 'components/common/Typography'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faArrowLeft, faChevronLeft, faChevronRight } from '@fortawesome/pro-regular-svg-icons'; +import { Button } from 'components/common/Input'; +import { Link } from 'components/common/Navigation'; +import { useLoaderData } from 'react-router-dom'; +import { engagementMetadata } from '../../../../../tests/unit/components/factory'; + +export const SuggestedEngagements = () => { + const [suggestedEngagements, setSuggestedEngagements] = React.useState([] as Engagement[]); + const [totalEngagements, setTotalEngagements] = React.useState(3); + const [page, setPage] = React.useState(0); + const [pageLength, setPageLength] = React.useState(3); + const [isLoading, setIsLoading] = React.useState(true); + const { engagement } = useLoaderData() as { engagement: Promise }; + const allEngagementsPromise = getEngagements({ size: 13, page: 1, include_banner_url: true }); + + const handleResize = () => { + if (window.innerWidth < 930) { + setPageLength(1); + } else if (window.innerWidth < 1500) { + setPageLength(2); + if (page > 5) { + setPage(5); + } + } else if (window.innerWidth < 1800) { + setPageLength(3); + if (page > 3) { + setPage(3); + } + } else { + setPageLength(4); + if (page > 4) { + setPage(4); + } + } + }; + useEffect(() => { + handleResize(); + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [page]); + + useEffect(() => { + const fetchData = async () => { + try { + // preload all 12 suggested engagements to keep things snappy + // a 13th engagement is loaded in case one of the 12 is the current engagement + const currentEngagement = await engagement; + const allEngagements = await allEngagementsPromise; + const filteredEngagements = allEngagements.items + .filter((engagement) => engagement.id !== currentEngagement.id) + .slice(0, 12) + .map((value) => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value); + setSuggestedEngagements(filteredEngagements); + setTotalEngagements(allEngagements.total); + setIsLoading(false); + } catch (error) { + console.error('Error fetching suggested engagements:', error); + } + }; + setPage(0); + fetchData(); + }, [engagement]); + + const pages = Math.ceil((totalEngagements - 1) / pageLength); + const engagementSlice = suggestedEngagements.slice(page * pageLength, (page + 1) * pageLength); + + return ( +
+ + + You may also be interested in + + + + {isLoading + ? Array.from({ length: 3 }).map((_, index) => ( + + + + )) + : engagementSlice.map((engagement, index) => ( + + + + ))} + + + theme.palette.primary.main as string, + }, + }, + }} + /> + + + theme.palette.text.primary, textDecoration: 'none' }}> + + All engagements + + + + +
+ ); +};