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

Mapper frontend login + remove temp auth from React frontend #1903

Merged
merged 27 commits into from
Nov 27, 2024
Merged
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
bf0bf3c
feat(osm-logo): logo add to mapper
NSUWAL123 Nov 20, 2024
aad8209
feat(login): login store add
NSUWAL123 Nov 20, 2024
36db3ea
fix(layer-switcher): close layer-switcher by default
NSUWAL123 Nov 20, 2024
5c15753
feat(login): login dialog popup add
NSUWAL123 Nov 20, 2024
ee8c818
fix(login): util functions add for login
NSUWAL123 Nov 20, 2024
37e1afb
fix(login): store current path on localStorage
NSUWAL123 Nov 20, 2024
b0ee2bf
fix(osmAuth): if user request redirection to mapper frontend then tre…
NSUWAL123 Nov 20, 2024
7af73f8
feat(logo): hot logo add
NSUWAL123 Nov 20, 2024
bd07a72
fix(header): header component ui slice
NSUWAL123 Nov 20, 2024
02aae8a
feat(login): toggle modal state add
NSUWAL123 Nov 20, 2024
683f737
fix(layout): replace hot-header with custom header component
NSUWAL123 Nov 20, 2024
788f7e8
fix(login): replace modalOpen state on parent with stores
NSUWAL123 Nov 20, 2024
67c3071
fix(+layout): barlow font add to root
NSUWAL123 Nov 21, 2024
b35f8da
feat(header): display username and profile image if user logged in
NSUWAL123 Nov 21, 2024
a8758a9
fix(login): signOut add
NSUWAL123 Nov 21, 2024
6201053
feat(drawerItems): drawer menu items add
NSUWAL123 Nov 21, 2024
7cd63ad
feat(header): drawer comp add, signout func add
NSUWAL123 Nov 21, 2024
ed25d68
fix(layerSwitchMenu): width & height fix
NSUWAL123 Nov 21, 2024
0908658
fix(mapLegends): fix(mapLegends): show map legend in dropdown
NSUWAL123 Nov 21, 2024
0557d67
fix(mapControlComponent): add mapLegend to mapControlComponent
NSUWAL123 Nov 21, 2024
9a5de99
fix(projectDetailsV2): remove showing map legend on accordion, btn li…
NSUWAL123 Nov 21, 2024
fc91a5e
remove(login): remove temporary login from react frontend
NSUWAL123 Nov 21, 2024
71cec9c
test: persist osm login across playwright tests (temp login removed) …
spwoodcock Nov 27, 2024
defa9b2
test: fix mapper flow tests, first open page, then click card
spwoodcock Nov 27, 2024
0d8d916
test: disable firefox and webkit tests entirely for now
spwoodcock Nov 27, 2024
9d4b8be
docs: add note about attempting test auth with webkit
spwoodcock Nov 27, 2024
13801d1
test: use btnTestId prop to identify specific button during tests
spwoodcock Nov 27, 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
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
Loading