Skip to content

Commit

Permalink
Add tests to OSM Teams integration
Browse files Browse the repository at this point in the history
  • Loading branch information
willemarcel committed Jul 5, 2023
1 parent 51de80b commit fcda1e3
Show file tree
Hide file tree
Showing 16 changed files with 274 additions and 36 deletions.
2 changes: 1 addition & 1 deletion backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def create_app(env="backend.config.EnvironmentConfig"):
env = "backend.config.TestEnvironmentConfig"
app.config.from_object(env)
# Enable logging to files
initialise_logger(app)
# initialise_logger(app)
app.logger.info("Starting up a new Tasking Manager application")

# Connect to database
Expand Down
1 change: 1 addition & 0 deletions backend/api/teams/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ def post(self):
- "ANY"
- "BY_REQUEST"
- "BY_INVITE"
- "OSM_TEAMS"
responses:
201:
description: Team created successfully
Expand Down
1 change: 1 addition & 0 deletions backend/models/dtos/team_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def validate_team_join_method(value):
f"{TeamJoinMethod.ANY.name}, "
f"{TeamJoinMethod.BY_INVITE.name}, "
f"{TeamJoinMethod.BY_REQUEST.name}"
f"{TeamJoinMethod.OSM_TEAMS.name}"
)


Expand Down
1 change: 1 addition & 0 deletions backend/models/postgis/statuses.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class TeamJoinMethod(Enum):
ANY = 0
BY_REQUEST = 1
BY_INVITE = 2
OSM_TEAMS = 3


class TeamRoles(Enum):
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/teamsAndOrgs/management.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export function JoinMethodBox(props) {
ANY: 'anyoneCanJoin',
BY_REQUEST: 'byRequest',
BY_INVITE: 'byInvite',
OSM_TEAMS: 'OSMTeams',
};
return (
<div className={`tc br1 f7 ttu ba red b--red ${props.className}`}>
Expand Down
8 changes: 3 additions & 5 deletions frontend/src/components/teamsAndOrgs/members.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,13 @@ export function Members({
<div className={`bg-white b--grey-light pa4 ${editMode ? 'bt bl br' : 'ba'}`}>
<div className="cf db">
<h3 className="f3 blue-dark mv2 fw6 fl">{title}</h3>
{!disableEdit &&
<EditModeControl editMode={editMode} switchModeFn={setEditMode} />
}
{!disableEdit && <EditModeControl editMode={editMode} switchModeFn={setEditMode} />}
</div>
{disableEdit &&
{disableEdit && (
<div className="blue-grey f6">
<FormattedMessage {...messages.syncedWithOSMTeams} />
</div>
}
)}
<div className="cf mb1">
{editMode && (
<AsyncSelect
Expand Down
17 changes: 13 additions & 4 deletions frontend/src/components/teamsAndOrgs/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,15 @@ export default defineMessages({
id: 'management.teams.join_method.by_invite.description',
defaultMessage: 'Users can only join this team if managers invite them.',
},
OSMTeams: {
id: 'management.teams.join_method.osm_teams',
defaultMessage: 'OSM Teams',
},
OSMTeamsDescription: {
id: 'management.teams.join_method.osm_teams.description',
defaultMessage:
'The users of this team are managed in OSM Teams. This option is automatically selected when you enable the sync with OSM Teams.',
},
public: {
id: 'management.teams.visibility.public',
defaultMessage: 'Public',
Expand Down Expand Up @@ -578,10 +587,6 @@ export default defineMessages({
id: 'management.teams.sync.update',
defaultMessage: 'Synchronize again',
},
confirmResync: {
id: 'management.teams.sync.confirm_resync',
defaultMessage: 'Confirm sync',
},
confirmSelection: {
id: 'management.teams.sync.confirm',
defaultMessage: 'Confirm selection',
Expand All @@ -599,6 +604,10 @@ export default defineMessages({
defaultMessage:
'{number, plural, one {It was not possible to add the user: {users}.} other {It was not possible to add the users: {users}.}}',
},
syncUsersGenericError: {
id: 'management.teams.sync.users.generic_error',
defaultMessage: 'It was not possible to add some users.',
},
syncUsersErrorExtra: {
id: 'management.teams.sync.users.error.extra',
defaultMessage:
Expand Down
19 changes: 15 additions & 4 deletions frontend/src/components/teamsAndOrgs/teamSync.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const TeamSync = ({
const token = useSelector((state) => state.auth.token);
const [errors, setErrors] = useState(searchParams?.get('syncUsersErrors'));
const [showSelectionModal, setShowSelectionModal] = useState(false);
const [isSyncing, setIsSyncing] = useState(false);
const reSyncParams = {
tmTeamId,
members,
Expand Down Expand Up @@ -141,11 +142,16 @@ export const TeamSync = ({
<>
<Button
className="ba b--red white bg-red mh1"
onClick={() => reSyncUsers(reSyncParams)}
loading={isSyncing}
onClick={() => {
setIsSyncing(true);
reSyncUsers(reSyncParams);
setIsSyncing(false);
}}
>
<FormattedMessage {...messages.updateUsers} />
</Button>
{errors?.length > 0 && (
{errors && (
<div
className="pt2 pointer"
role="button"
Expand All @@ -154,9 +160,14 @@ export const TeamSync = ({
>
<Alert type="error">
<FormattedMessage
{...messages.syncUsersError}
{...messages[
typeof errors === 'object' ? 'syncUsersError' : 'syncUsersGenericError'
]}
values={{
users: errors.map((u) => u.username).join(' ,'),
users:
typeof errors === 'object'
? errors.map((u) => u.username).join(', ')
: [],
number: errors.length,
}}
/>
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/teamsAndOrgs/teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export function TeamInformation({ disableJoinMethodField }) {
ANY: 'anyoneCanJoin',
BY_REQUEST: 'byRequest',
BY_INVITE: 'byInvite',
OSM_TEAMS: 'OSMTeams',
};

return (
Expand Down Expand Up @@ -199,7 +200,7 @@ export function TeamInformation({ disableJoinMethodField }) {
name="joinMethod"
value={method}
required
disabled={disableJoinMethodField}
disabled={disableJoinMethodField || method === 'OSM_TEAMS'}
/>
<span className="f5">
<FormattedMessage {...messages[joinMethods[method]]} />
Expand All @@ -214,7 +215,7 @@ export function TeamInformation({ disableJoinMethodField }) {
</div>
))}
</div>
{formState.values.joinMethod === 'BY_INVITE' && (
{['BY_INVITE', 'OSM_TEAMS'].includes(formState.values.joinMethod) && (
<div className="cf pt1">
<label className={labelClasses}>
<FormattedMessage {...messages.visibility} />
Expand Down
6 changes: 1 addition & 5 deletions frontend/src/hooks/UseOSMTeams.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ const useFetchExternal = (url, trigger = true, token) => {
setLoading(true);
try {
// replace in locale is needed because the backend uses underscore instead of dash
const response = await fetchExternalJSONAPI(
url,
token,
'GET',
);
const response = await fetchExternalJSONAPI(url, token, 'GET');
setData(response);
setLoading(false);
} catch (e) {
Expand Down
79 changes: 79 additions & 0 deletions frontend/src/network/tests/mockData/osmTeams.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
export const myTeams = {
data: [
{
id: 1,
name: 'OSM Teams Developers',
hashtag: null,
bio: null,
privacy: 'private',
require_join_request: false,
updated_at: '2023-06-11T15:37:57.793Z',
created_at: '2022-05-06T16:10:18.452Z',
location: '{"type":"Point","coordinates":[-77.02438,38.906337]}',
members: '8',
},
{
id: 10,
name: 'SOTMUS 2023',
hashtag: '#sotmus',
bio: 'Attendees of State of the Map 2023 in Richmond, VA',
privacy: 'public',
require_join_request: false,
updated_at: '2023-06-09T20:00:51.108Z',
created_at: '2023-06-09T17:01:41.376Z',
location: '{"type":"Point","coordinates":[-77.4508325,37.548201459]}',
members: '27',
},
{
id: 20,
name: 'My Friends',
hashtag: null,
bio: null,
privacy: 'private',
require_join_request: false,
updated_at: '2022-11-17T15:32:58.615Z',
created_at: '2022-11-17T15:32:58.615Z',
location: null,
members: '2',
},
],
pagination: { total: 3, lastPage: 1, perPage: 10, currentPage: 1, from: 0, to: 3 },
};

export const osmTeam1 = {
id: 73,
name: 'OSM Teams Developers',
hashtag: '#OSMDevs',
bio: 'OSM Team Developers',
privacy: 'private',
require_join_request: false,
updated_at: '2023-03-13T18:05:23.679Z',
created_at: '2022-05-06T16:10:18.452Z',
location: null,
org: { organization_id: 5, name: 'Development Seed' },
requesterIsMember: true,
};

export const osmTeamMembers = {
teamId: 73,
members: {
data: [
{ id: 146675, name: 'geohacker' },
{ id: 2454337, name: 'kamicut' },
{ id: 2206554, name: 'LsGoodman' },
{ id: 10139859, name: 'MarcFarra' },
{ id: 261012, name: 'sanjayb' },
{ id: 62817, name: 'vgeorge' },
{ id: 15547551, name: 'Vgeorge2' },
{ id: 360183, name: 'wille' },
],
pagination: { total: 8, lastPage: 1, perPage: 10, currentPage: 1, from: 0, to: 8 },
},
};

export const osmTeamModerators = [
{ id: 64, team_id: 73, osm_id: 2454337 },
{ id: 443, team_id: 73, osm_id: 15547551 },
{ id: 459, team_id: 73, osm_id: 146675 },
{ id: 464, team_id: 73, osm_id: 2206554 },
];
2 changes: 1 addition & 1 deletion frontend/src/network/tests/mockData/teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const teamCreationSuccess = {
teamId: 123,
};

export const teamUpdationSuccess = {
export const teamUpdateSuccess = {
Status: 'Updated',
};

Expand Down
20 changes: 17 additions & 3 deletions frontend/src/network/tests/server-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ import {
teams,
team,
teamCreationSuccess,
teamUpdationSuccess,
teamUpdateSuccess,
teamDeletionSuccess,
} from './mockData/teams';
import { userTasks } from './mockData/tasksStats';
import { homepageStats } from './mockData/homepageStats';
import { banner, countries, josmRemote, systemStats } from './mockData/miscellaneous';
import tasksGeojson from '../../utils/tests/snippets/tasksGeometry';
import { API_URL } from '../../config';
import { API_URL, OSM_TEAMS_API_URL } from '../../config';
import { notifications, ownCountUnread } from './mockData/notifications';
import { authLogin, setUser, userRegister } from './mockData/auth';
import {
Expand All @@ -74,6 +74,7 @@ import {
submitValidationTask,
userLockedTasks,
} from './mockData/taskHistory';
import { myTeams, osmTeam1, osmTeamMembers, osmTeamModerators } from './mockData/osmTeams';

const handlers = [
rest.get(API_URL + 'projects/:id/queries/summary/', async (req, res, ctx) => {
Expand Down Expand Up @@ -242,7 +243,7 @@ const handlers = [
return res(ctx.json(teamCreationSuccess));
}),
rest.patch(API_URL + 'teams/:id/', (req, res, ctx) => {
return res(ctx.json(teamUpdationSuccess));
return res(ctx.json(teamUpdateSuccess));
}),
rest.delete(API_URL + 'teams/:id', (req, res, ctx) => {
return res(ctx.json(teamDeletionSuccess));
Expand Down Expand Up @@ -357,6 +358,19 @@ const handlers = [
rest.get('http://127.0.0.1:8111/version', (req, res, ctx) => {
return res(ctx.json(josmRemote));
}),
// OSM Teams
rest.get(OSM_TEAMS_API_URL + '/api/my/teams', (req, res, ctx) => {
return res(ctx.json(myTeams));
}),
rest.get(OSM_TEAMS_API_URL + '/api/teams/:id', (req, res, ctx) => {
return res(ctx.json(osmTeam1));
}),
rest.get(OSM_TEAMS_API_URL + '/api/teams/:id/members', (req, res, ctx) => {
return res(ctx.json(osmTeamMembers));
}),
rest.get(OSM_TEAMS_API_URL + '/api/teams/:id/moderators', (req, res, ctx) => {
return res(ctx.json(osmTeamModerators));
}),
];

const failedToConnectError = (req, res, ctx) => {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/routes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom';

import { Root } from './views/root';
import { Authorized } from './views/authorized';
import { Authorized, OSMTeamsAuthorized } from './views/authorized';
import { NotFound } from './views/notFound';
import { FallbackComponent } from './views/fallback';
import { Redirect } from './components/redirect';
Expand Down Expand Up @@ -198,6 +198,7 @@ export const router = createBrowserRouter(
}}
/>
<Route path="authorized" element={<Authorized />} />
<Route path="osmteams-authorized" element={<OSMTeamsAuthorized />} />
<Route
path="login"
lazy={async () => {
Expand Down
18 changes: 9 additions & 9 deletions frontend/src/views/teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,10 @@ export function CreateTeam() {
const errors = [];
Promise.all([
...managers
.filter((user) => user.username !== userDetails.username)
.map((user) =>
.filter((user) => user.username !== userDetails.username)
.map((user) =>
joinTeamRequest(result.teamId, user.username, 'MANAGER', token).catch((e) =>
errors.push({ username: user.username, function: 'MANAGER' }),
errors.push({ username: user.username, function: 'MANAGER' }),
),
),
...members.map((user) =>
Expand All @@ -212,7 +212,7 @@ export function CreateTeam() {
);
navigate(`/manage/teams/${result.teamId}${additionalSearchParam}`);
});
})
})
.catch(() => setIsError(true));
};

Expand All @@ -221,7 +221,7 @@ export function CreateTeam() {
onSubmit={(values) => createTeam(values)}
initialValues={{ visibility: 'PUBLIC' }}
render={({ handleSubmit, pristine, submitting, values }) => {
if (osmTeamsId) values.joinMethod = 'BY_INVITE';
if (osmTeamsId) values.joinMethod = 'OSM_TEAMS';
return (
<form onSubmit={handleSubmit} className="blue-grey">
<div className="cf pb5">
Expand Down Expand Up @@ -403,12 +403,12 @@ export function EditTeam() {
const onUpdateTeamFailure = () => setIsError(true);

const updateTeam = (payload) => {
if (payload.joinMethod !== 'BY_INVITE') {
if (['ANY', 'BY_REQUEST'].includes(payload.joinMethod)) {
payload.visibility = 'PUBLIC';
}
payload.osm_teams_id = osmTeamsId;
// force teams synced with OSM Teams to have BY_INVITE join method
if (osmTeamsId) payload.joinMethod = 'BY_INVITE';
// force teams synced with OSM Teams to have OSM_TEAMS join method
if (osmTeamsId) payload.joinMethod = 'OSM_TEAMS';

updateEntity(`teams/${id}/`, 'team', payload, token, forceUpdate, onUpdateTeamFailure);
};
Expand Down Expand Up @@ -490,7 +490,7 @@ export function EditTeam() {
updateTeam={(selectedTeamId) =>
pushToLocalJSONAPI(
`teams/${id}/`,
JSON.stringify({ osm_teams_id: selectedTeamId, joinMethod: 'BY_INVITE' }),
JSON.stringify({ osm_teams_id: selectedTeamId, joinMethod: 'OSM_TEAMS' }),
token,
'PATCH',
)
Expand Down
Loading

0 comments on commit fcda1e3

Please sign in to comment.