Skip to content

Commit

Permalink
feat(mapper): frontend login + remove temp auth from React frontend (#…
Browse files Browse the repository at this point in the history
…1903)

* feat(osm-logo): logo add to mapper

* feat(login): login store add

* fix(layer-switcher): close layer-switcher by default

* feat(login): login dialog popup add

* fix(login): util functions add for login

* fix(login): store current path on localStorage

* fix(osmAuth): if user request redirection to mapper frontend then treat as external url

* feat(logo): hot logo add

* fix(header): header component ui slice

* feat(login): toggle modal state add

* fix(layout): replace hot-header with custom header component

* fix(login): replace modalOpen state on parent with stores

* fix(+layout): barlow font add to root

* feat(header): display username and profile image if user logged in

* fix(login): signOut add

* feat(drawerItems): drawer menu items add

* feat(header): drawer comp add, signout func add

* fix(layerSwitchMenu): width & height fix

* fix(mapLegends): fix(mapLegends): show map legend in dropdown

* fix(mapControlComponent): add mapLegend to mapControlComponent

* fix(projectDetailsV2): remove showing map legend on accordion, btn linking to mapper frontend add

* remove(login): remove temporary login from react frontend

* test: persist osm login across playwright tests (temp login removed) (#1910)

* test: wip persist osm login across playwright tests

* test: add temp auth config as a playwright workaround

* test: fix mapper flow tests, first open page, then click card

* test: disable firefox and webkit tests entirely for now

* docs: add note about attempting test auth with webkit

* test: use btnTestId prop to identify specific button during tests

---------

Co-authored-by: Sam <[email protected]>
Co-authored-by: spwoodcock <[email protected]>
  • Loading branch information
3 people authored Nov 27, 2024
1 parent c185d65 commit d175979
Show file tree
Hide file tree
Showing 28 changed files with 526 additions and 122 deletions.
1 change: 0 additions & 1 deletion src/backend/app/auth/auth_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,6 @@ async def temp_login(
setting it as a cookie.
Args:
request (Request): The incoming request object.
email: email of non-osm user.
Returns:
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,5 @@ dist
/playwright-report/
/blob-report/
/playwright/.cache/

playwright/.auth
/e2e/.cache/
e2e/.auth
8 changes: 2 additions & 6 deletions src/frontend/e2e/01-create-new-project.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,14 @@

import { test, expect } from '@playwright/test';

import { tempLogin } from './helpers';

test('create new project', async ({ browserName, page }) => {
// Specific for this large test, only run in one browser
// (playwright.config.ts is configured to run all browsers by default)
test.skip(browserName !== 'chromium', 'Test only for chromium!');

// 0. Temp Login
await tempLogin(page);
await page.getByRole('button', { name: '+ Create New Project' }).click();

// 1. Project Details Step
await page.goto('/');
await page.getByRole('button', { name: '+ Create New Project' }).click();
await page.getByRole('button', { name: 'NEXT' }).click();
await expect(page.getByText('Project Name is Required.')).toBeVisible();
await expect(page.getByText('Short Description is Required.', { exact: true })).toBeVisible();
Expand Down
21 changes: 6 additions & 15 deletions src/frontend/e2e/02-mapper-flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,16 @@

import { test, expect } from '@playwright/test';

import { tempLogin, openTestProject } from './helpers';
import { openTestProject } from './helpers';

test.describe('mapper flow', () => {
test('task actions', async ({ browserName, page }) => {
// Specific for this large test, only run in one browser
// (playwright.config.ts is configured to run all browsers by default)
test.skip(browserName !== 'chromium', 'Test only for chromium!');

// 0. Temp Login
await tempLogin(page);
await openTestProject(page);

// 1. Click on task area on map
await openTestProject(page);
await page.locator('canvas').click({
position: {
x: 445,
Expand All @@ -35,8 +32,8 @@ test.describe('mapper flow', () => {
});

// 2. Lock task for mapping
await expect(page.getByRole('button', { name: 'START MAPPING' })).toBeVisible();
await page.getByRole('button', { name: 'START MAPPING' }).click();
await expect(page.getByTestId('StartMapping')).toBeVisible();
await page.getByTestId('StartMapping').click();
await page.waitForSelector('div:has-text("updated to LOCKED_FOR_MAPPING"):nth-of-type(1)');
await expect(
page
Expand Down Expand Up @@ -107,20 +104,17 @@ test.describe('mapper flow', () => {
// (playwright.config.ts is configured to run all browsers by default)
test.skip(browserName !== 'chromium', 'Test only for chromium!');

// 0. Temp Login
await tempLogin(page);
await openTestProject(page);

// 1. Click on task area on map
// click on task & assert task popup visibility
await openTestProject(page);
await page.locator('canvas').click({
position: {
x: 388,
y: 220,
},
});
await expect(page.getByText('Status: UNLOCKED_TO_MAP')).toBeVisible();
await expect(page.getByRole('button', { name: 'START MAPPING' })).toBeVisible();
await expect(page.getByTestId('StartMapping')).toBeVisible();

// 2. Click on a specific feature / Entity within a task
// assert feature popup visibility
Expand Down Expand Up @@ -176,10 +170,7 @@ test.describe('mapper flow', () => {
// (playwright.config.ts is configured to run all browsers by default)
test.skip(browserName !== 'chromium', 'Test only for chromium!');

// 0. Temp Login
await tempLogin(page);
await openTestProject(page);

await page.locator('canvas').click({
position: {
x: 475,
Expand Down
23 changes: 23 additions & 0 deletions src/frontend/e2e/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { test as setup } from '@playwright/test';
import path from 'path';

const authFile = path.join(__dirname, './.auth/user.json');

setup('authenticate', async ({ browserName, page }) => {
// Note here we only run in chromium, to avoid running this setup step
// for Firefox and Webkit.
// This is because Webkit does not respect 'secure' cookies on http contexts.
// For this to work we would need to configure https for testing
// https://github.com/hotosm/fmtm/pull/1920
setup.skip(browserName !== 'chromium', 'Test only for chromium!');

// Note this sets a token so we can proceed, but the login will be
// overwritten by svcfmtm localadmin user (as DEBUG=True)
await page.goto('/playwright-temp-login/');

// Now check we are signed in as localadmin
await page.waitForSelector('text=localadmin');

// Save authentication state
await page.context().storageState({ path: authFile });
});
7 changes: 1 addition & 6 deletions src/frontend/e2e/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { Page } from '@playwright/test';

export async function tempLogin(page: Page) {
await page.goto('/');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByText('Temporary Account').click();
}

export async function openTestProject(page: Page) {
await page.goto('/');
// open project card with regex text 'Project Create Playwright xxx'
await page
.getByText(/^Project Create Playwright/)
Expand Down
32 changes: 23 additions & 9 deletions src/frontend/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,32 @@ export default defineConfig({

/* Configure projects for major browsers */
projects: [
// Setup project
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: { browserName: 'chromium' },
},
{
name: 'firefox',
use: { browserName: 'firefox' },
},
{
name: 'webkit',
use: { browserName: 'webkit' },
use: {
browserName: 'chromium',
storageState: 'e2e/.auth/user.json',
},
dependencies: ['setup'],
},
// {
// name: 'firefox',
// use: {
// browserName: 'firefox',
// storageState: 'e2e/.auth/user.json',
// },
// dependencies: ['setup'],
// },
// {
// name: 'webkit',
// use: {
// browserName: 'webkit',
// storageState: 'playwright/.auth/user.json',
// },
// dependencies: ['setup'],
// },

/* Test against mobile viewports. */
// {
Expand Down
30 changes: 0 additions & 30 deletions src/frontend/src/api/Login.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/frontend/src/components/DialogTaskActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export default function Dialog({ taskId, feature }: dialogPropType) {
return list_of_task_actions?.length != 0 ? (
<Button
btnId={data.value}
btnTestId="StartMapping"
key={index}
onClick={(e) => {
if (
Expand Down
20 changes: 3 additions & 17 deletions src/frontend/src/components/LoginPopup.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React, { useEffect } from 'react';
import React from 'react';
import CoreModules from '@/shared/CoreModules';
import { Modal } from '@/components/common/Modal';
import { useDispatch } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
import { LoginActions } from '@/store/slices/LoginSlice';
import { osmLoginRedirect } from '@/utilfunctions/login';
import { TemporaryLoginService } from '@/api/Login';
import AssetModules from '@/shared/AssetModules';
import OSMImg from '@/assets/images/osm-logo.png';

type loginOptionsType = {
Expand All @@ -24,17 +22,10 @@ const loginOptions: loginOptionsType[] = [
image: OSMImg,
description: 'Edits made in FMTM will be credited to your OSM account.',
},
{
id: 'temp_account',
name: 'Temporary Account',
icon: <AssetModules.PersonIcon color="" sx={{ fontSize: '40px' }} className="fmtm-w-10 fmtm-h-10" />,
description: "If you're not an OSM user or prefer not to create an OSM account.",
},
];

const LoginPopup = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from || '/';

Expand All @@ -44,18 +35,13 @@ const LoginPopup = () => {
if (selectedOption === 'osm_account') {
localStorage.setItem('requestedPath', from);
osmLoginRedirect();
} else {
await dispatch(TemporaryLoginService(`${import.meta.env.VITE_API_URL}/auth/temp-login`, from));
dispatch(LoginActions.setLoginModalOpen(false));
navigate(from);
}
};

const LoginDescription = () => {
return (
<div className="fmtm-flex fmtm-items-start fmtm-flex-col">
<div className="fmtm-text-2xl fmtm-font-bold fmtm-mb-1">Sign In</div>
<div className="fmtm-text-base fmtm-mb-5 fmtm-text-gray-700">Select an account type to sign in</div>
<div className="fmtm-text-2xl fmtm-font-bold fmtm-mb-4">Sign In</div>
<div className="fmtm-w-full fmtm-flex fmtm-flex-col fmtm-gap-4 fmtm-justify-items-center">
{loginOptions?.map((option) => (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const LayerSwitchMenu = ({ map, pmTileLayerData = null }: { map: any; pmTileLaye
backgroundImage: activeLayer === 'None' ? 'none' : `url(${layerIcons[activeLayer] || satelliteImg})`,
backgroundColor: 'white',
}}
className={`fmtm-relative fmtm-group fmtm-order-4 fmtm-w-10 fmtm-h-10 fmtm-border-[1px] fmtm-border-primaryRed hover:fmtm-border-[2px] fmtm-duration-75 fmtm-cursor-pointer fmtm-bg-contain fmtm-rounded-full ${
className={`fmtm-relative fmtm-group fmtm-order-4 fmtm-w-9 fmtm-h-9 fmtm-border-[1px] fmtm-border-primaryRed hover:fmtm-border-[2px] fmtm-duration-75 fmtm-cursor-pointer fmtm-bg-contain fmtm-rounded-full ${
activeLayer === 'None' ? '!fmtm-border-primaryRed' : ''
}`}
></div>
Expand Down
42 changes: 35 additions & 7 deletions src/frontend/src/components/MapLegends.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import React from 'react';
import AssetModules from '@/shared/AssetModules';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenuPortal,
} from '@/components/common/Dropdown';
import { Tooltip } from '@mui/material';

type mapDetialsType = {
value: string;
Expand Down Expand Up @@ -66,13 +73,34 @@ const MapLegends = ({ defaultTheme }: { defaultTheme: any }) => {
};

return (
<div className="fmtm-py-3">
<div className="fmtm-flex fmtm-flex-col fmtm-gap-2">
{MapDetails.map((data, index) => {
return <LegendListItem data={data} key={index} />;
})}
</div>
</div>
<DropdownMenu modal={false}>
<DropdownMenuTrigger className="fmtm-outline-none">
<Tooltip title="Legend Toggle" placement="left">
<div
className={`fmtm-bg-white fmtm-rounded-full hover:fmtm-bg-gray-100 fmtm-cursor-pointer fmtm-duration-300 fmtm-w-9 fmtm-h-9 fmtm-min-h-9 fmtm-min-w-9 fmtm-max-w-9 fmtm-max-h-9 fmtm-flex fmtm-justify-center fmtm-items-center `}
>
<AssetModules.LegendToggleIcon />
</div>
</Tooltip>
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent
className="fmtm-px-2 fmtm-border-none fmtm-z-[60px] fmtm-bg-white"
align="end"
alignOffset={100}
sideOffset={-42}
>
<div className="fmtm-py-3">
<p className="fmtm-mb-3">Legend</p>
<div className="fmtm-flex fmtm-flex-col fmtm-gap-2">
{MapDetails.map((data, index) => {
return <LegendListItem data={data} key={index} />;
})}
</div>
</div>{' '}
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenu>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ProjectOptions from '@/components/ProjectDetailsV2/ProjectOptions';
import useOutsideClick from '@/hooks/useOutsideClick';
import LayerSwitchMenu from '../MapComponent/OpenLayersComponent/LayerSwitcher/LayerSwitchMenu';
import { Tooltip } from '@mui/material';
import MapLegends from '@/components/MapLegends';

type mapControlComponentType = {
map: any;
Expand Down Expand Up @@ -45,6 +46,7 @@ const MapControlComponent = ({ map, projectName, pmTileLayerData }: mapControlCo
const dispatch = CoreModules.useAppDispatch();
const [toggleCurrentLoc, setToggleCurrentLoc] = useState(false);
const geolocationStatus = useAppSelector((state) => state.project.geolocationStatus);
const defaultTheme = useAppSelector((state) => state.theme.hotTheme);
const [divRef, toggle, handleToggle] = useOutsideClick();

const handleOnClick = (btnId) => {
Expand Down Expand Up @@ -88,13 +90,14 @@ const MapControlComponent = ({ map, projectName, pmTileLayerData }: mapControlCo
</Tooltip>
))}
<LayerSwitchMenu map={map} pmTileLayerData={pmTileLayerData} />
{/* download options */}
<div
className={`fmtm-relative ${!pathname.includes('project/') ? 'fmtm-hidden' : 'sm:fmtm-hidden'}`}
ref={divRef}
>
<div
onClick={() => handleToggle()}
className="fmtm-bg-white fmtm-rounded-full fmtm-p-2 hover:fmtm-bg-gray-100 fmtm-cursor-pointer fmtm-duration-300 "
className="fmtm-bg-white fmtm-rounded-full hover:fmtm-bg-gray-100 fmtm-cursor-pointer fmtm-duration-300 fmtm-w-9 fmtm-h-9 fmtm-min-h-9 fmtm-min-w-9 fmtm-max-w-9 fmtm-max-h-9 fmtm-flex fmtm-justify-center fmtm-items-center"
>
<AssetModules.FileDownloadIcon />
</div>
Expand All @@ -108,6 +111,7 @@ const MapControlComponent = ({ map, projectName, pmTileLayerData }: mapControlCo
<ProjectOptions projectName={projectName} />
</div>
</div>
<MapLegends defaultTheme={defaultTheme} />
</div>
);
};
Expand Down
Loading

0 comments on commit d175979

Please sign in to comment.