diff --git a/.circleci/config.yml b/.circleci/config.yml index acf07236ec..e03c3dd212 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,7 +32,7 @@ jobs: name: Run yarn test command: | cd ${CIRCLE_WORKING_DIRECTORY}/frontend/ - CI=true REACT_APP_OSM_TEAMS_CLIENT_ID=boo yarn test -w 1 + CI=true yarn test -w 1 CI=true GENERATE_SOURCEMAP=false yarn build backend-code-check-PEP8: diff --git a/backend/__init__.py b/backend/__init__.py index 49995de8d3..4e19622fde 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -80,10 +80,6 @@ def format_url(endpoint): scope=EnvironmentConfig.OAUTH_SCOPE, redirect_uri=EnvironmentConfig.OAUTH_REDIRECT_URI, ) -osm_teams = OAuth2Session( - client_id=EnvironmentConfig.OSM_TEAMS_CLIENT_ID, - scope="openid offline", -) # Import all models so that they are registered with SQLAlchemy from backend.models.postgis import * # noqa @@ -380,8 +376,6 @@ def add_api_endpoints(app): SystemAuthenticationEmailAPI, SystemAuthenticationLoginAPI, SystemAuthenticationCallbackAPI, - OSMTeamsAuthenticationCallbackAPI, - OSMTeamsAuthenticationAPI, ) from backend.api.system.applications import SystemApplicationsRestAPI from backend.api.system.image_upload import SystemImageUploadRestAPI @@ -934,16 +928,9 @@ def add_api_endpoints(app): api.add_resource( SystemAuthenticationLoginAPI, format_url("system/authentication/login/") ) - api.add_resource( - OSMTeamsAuthenticationAPI, format_url("system/osm-teams-authentication/login/") - ) api.add_resource( SystemAuthenticationCallbackAPI, format_url("system/authentication/callback/") ) - api.add_resource( - OSMTeamsAuthenticationCallbackAPI, - format_url("system/osm-teams-authentication/callback/"), - ) api.add_resource( SystemAuthenticationEmailAPI, format_url("system/authentication/email/") ) diff --git a/backend/api/system/authentication.py b/backend/api/system/authentication.py index 6bee27bb8a..58453ddad6 100644 --- a/backend/api/system/authentication.py +++ b/backend/api/system/authentication.py @@ -2,7 +2,7 @@ from flask_restful import Resource from oauthlib.oauth2.rfc6749.errors import InvalidGrantError -from backend import osm, osm_teams +from backend import osm from backend.config import EnvironmentConfig from backend.services.users.authentication_service import ( AuthenticationService, @@ -42,33 +42,6 @@ def get(self): return {"auth_url": login_url, "state": state}, 200 -class OSMTeamsAuthenticationAPI(Resource): - def get(self): - """ - Returns URL to allow authentication in OSM Teams - --- - tags: - - system - produces: - - application/json - parameters: - - in: query - name: redirect_uri - description: Route to redirect user once authenticated - type: string - default: /take/me/here - responses: - 200: - description: oauth2 params - """ - state = AuthenticationService.generate_random_state() - osm_teams.state = state - login_url, state = osm_teams.authorization_url( - EnvironmentConfig.OSM_TEAMS_AUTH_URL - ) - return {"auth_url": login_url, "state": state}, 200 - - class SystemAuthenticationCallbackAPI(Resource): def get(self): """ @@ -152,69 +125,6 @@ def get(self): return {"Error": "Unable to authenticate", "SubCode": "AuthError"}, 500 -class OSMTeamsAuthenticationCallbackAPI(Resource): - def get(self): - """ - Handles the OSM Teams OAuth callback - --- - tags: - - system - produces: - - application/json - parameters: - - in: query - name: redirect_uri - description: Route to redirect user once authenticated - type: string - default: /take/me/here - required: false - - in: query - name: code - description: Code obtained after user authorization - type: string - required: true - - in: query - name: email_address - description: Email address to used for email notifications from TM. - type: string - required: false - responses: - 302: - description: Redirects to login page, or login failed page - 500: - description: A problem occurred authenticating the user - 502: - description: A problem occurred negotiating with the OSM API - """ - - authorization_code = request.args.get("code", None) - if authorization_code is None: - return {"Subcode": "InvalidData", "Error": "Missing code parameter"}, 500 - - try: - osm_teams_response = osm_teams.fetch_token( - token_url=EnvironmentConfig.OSM_TEAMS_TOKEN_URL, - client_secret=EnvironmentConfig.OSM_TEAMS_CLIENT_SECRET, - code=authorization_code, - ) - except InvalidGrantError: - return { - "Error": "The provided authorization grant is invalid, expired or revoked", - "SubCode": "InvalidGrantError", - }, 400 - if osm_teams_response is None: - current_app.logger.critical("Couldn't obtain token from OSM Teams.") - return { - "Subcode": "TokenFetchError", - "Error": "Couldn't fetch token from OSM Teams.", - }, 502 - - try: - return osm_teams_response, 200 - except AuthServiceError: - return {"Error": "Unable to authenticate", "SubCode": "AuthError"}, 500 - - class SystemAuthenticationEmailAPI(Resource): def get(self): """ diff --git a/backend/api/teams/resources.py b/backend/api/teams/resources.py index 64d6d16e6f..06df030d27 100644 --- a/backend/api/teams/resources.py +++ b/backend/api/teams/resources.py @@ -45,8 +45,6 @@ def patch(self, team_id): name: type: string default: HOT - Mappers - osm_teams_id: - type: integer logo: type: string default: https://tasks.hotosm.org/assets/img/hot-tm-logo.svg @@ -320,8 +318,6 @@ def post(self): organisation_id: type: integer default: 1 - osm_teams_id: - type: integer description: type: string visibility: @@ -335,7 +331,6 @@ def post(self): - "ANY" - "BY_REQUEST" - "BY_INVITE" - - "OSM_TEAMS" responses: 201: description: Team created successfully diff --git a/backend/config.py b/backend/config.py index 35958276c6..555205014f 100644 --- a/backend/config.py +++ b/backend/config.py @@ -237,17 +237,6 @@ class EnvironmentConfig: # Sentry backend DSN SENTRY_BACKEND_DSN = os.getenv("TM_SENTRY_BACKEND_DSN", None) - # OSM Teams - OSM_TEAMS_CLIENT_ID = os.getenv("OSM_TEAMS_CLIENT_ID", None) - OSM_TEAMS_CLIENT_SECRET = os.getenv("OSM_TEAMS_CLIENT_SECRET", None) - OSM_TEAMS_AUTH_DOMAIN = os.getenv("OSM_TEAMS_AUTH_DOMAIN", None) - OSM_TEAMS_TOKEN_DOMAIN = os.getenv("OSM_TEAMS_TOKEN_DOMAIN", OSM_TEAMS_AUTH_DOMAIN) - OSM_TEAMS_AUTH_PATH = os.getenv("OSM_TEAMS_AUTH_PATH", "/hyauth/oauth2/auth") - OSM_TEAMS_TOKEN_PATH = os.getenv("OSM_TEAMS_TOKEN_PATH", "/hyauth/oauth2/token") - OSM_TEAMS_AUTH_URL = f"{OSM_TEAMS_AUTH_DOMAIN}{OSM_TEAMS_AUTH_PATH}" - OSM_TEAMS_TOKEN_URL = f"{OSM_TEAMS_TOKEN_DOMAIN}{OSM_TEAMS_TOKEN_PATH}" - OSM_TEAMS_API_URL = os.getenv("OSM_TEAMS_API_URL", None) - class TestEnvironmentConfig(EnvironmentConfig): POSTGRES_TEST_DB = os.getenv("POSTGRES_TEST_DB", None) diff --git a/backend/models/dtos/team_dto.py b/backend/models/dtos/team_dto.py index d0b266ab60..58f2ee692b 100644 --- a/backend/models/dtos/team_dto.py +++ b/backend/models/dtos/team_dto.py @@ -39,7 +39,6 @@ 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}" ) @@ -93,7 +92,6 @@ def __init__(self): """ Describes JSON model for a team """ team_id = IntType(serialized_name="teamId") organisation_id = IntType(required=True) - osm_teams_id = IntType(required=False) organisation = StringType(required=True) organisation_slug = StringType(serialized_name="organisationSlug") name = StringType(required=True) @@ -133,7 +131,6 @@ class TeamDTO(Model): members = ListType(ModelType(TeamMembersDTO)) members_count = IntType(serialized_name="membersCount", required=False) managers_count = IntType(serialized_name="managersCount", required=False) - osm_teams_id = IntType(required=False) class TeamsListDTO(Model): @@ -153,7 +150,6 @@ class NewTeamDTO(Model): creator = LongType(required=True) organisation_id = IntType(required=True) name = StringType(required=True) - osm_teams_id = IntType() description = StringType() join_method = StringType( required=True, @@ -170,7 +166,6 @@ class UpdateTeamDTO(Model): creator = LongType() team_id = IntType() - osm_teams_id = IntType() organisation = StringType() organisation_id = IntType() name = StringType() diff --git a/backend/models/postgis/statuses.py b/backend/models/postgis/statuses.py index 57c70b7651..ef78498948 100644 --- a/backend/models/postgis/statuses.py +++ b/backend/models/postgis/statuses.py @@ -130,7 +130,6 @@ class TeamJoinMethod(Enum): ANY = 0 BY_REQUEST = 1 BY_INVITE = 2 - OSM_TEAMS = 3 class TeamRoles(Enum): diff --git a/backend/models/postgis/team.py b/backend/models/postgis/team.py index eb208c2ba6..ca9ac2a8f9 100644 --- a/backend/models/postgis/team.py +++ b/backend/models/postgis/team.py @@ -78,7 +78,6 @@ class Team(db.Model): visibility = db.Column( db.Integer, default=TeamVisibility.PUBLIC.value, nullable=False ) - osm_teams_id = db.Column(db.BigInteger, nullable=True) organisation = db.relationship(Organisation, backref="teams") @@ -96,7 +95,6 @@ def create_from_dto(cls, new_team_dto: NewTeamDTO): new_team.description = new_team_dto.description new_team.join_method = TeamJoinMethod[new_team_dto.join_method].value new_team.visibility = TeamVisibility[new_team_dto.visibility].value - new_team.osm_teams_id = new_team_dto.osm_teams_id org = Organisation.get(new_team_dto.organisation_id) new_team.organisation = org @@ -199,7 +197,6 @@ def as_dto(self): team_dto.name = self.name team_dto.organisation = self.organisation.name team_dto.organisation_id = self.organisation.id - team_dto.osm_teams_id = self.osm_teams_id team_dto.logo = self.organisation.logo team_dto.visibility = TeamVisibility(self.visibility).name return team_dto @@ -210,7 +207,6 @@ def as_dto_inside_org(self): team_dto.team_id = self.id team_dto.name = self.name team_dto.description = self.description - team_dto.osm_teams_id = self.osm_teams_id team_dto.join_method = TeamJoinMethod(self.join_method).name team_dto.members = self._get_team_members() team_dto.visibility = TeamVisibility(self.visibility).name diff --git a/backend/services/team_service.py b/backend/services/team_service.py index 68bfae00d7..6eeaf7b5b3 100644 --- a/backend/services/team_service.py +++ b/backend/services/team_service.py @@ -306,7 +306,6 @@ def get_all_teams(search_dto: TeamSearchDTO) -> TeamsListDTO: team_dto.join_method = TeamJoinMethod(team.join_method).name team_dto.visibility = TeamVisibility(team.visibility).name team_dto.description = team.description - team_dto.osm_teams_id = team.osm_teams_id team_dto.logo = team.organisation.logo team_dto.organisation = team.organisation.name team_dto.organisation_id = team.organisation.id @@ -346,7 +345,6 @@ def get_team_as_dto( team_dto.join_method = TeamJoinMethod(team.join_method).name team_dto.visibility = TeamVisibility(team.visibility).name team_dto.description = team.description - team_dto.osm_teams_id = team.osm_teams_id team_dto.logo = team.organisation.logo team_dto.organisation = team.organisation.name team_dto.organisation_id = team.organisation.id diff --git a/example.env b/example.env index d6875f8749..9045620a33 100644 --- a/example.env +++ b/example.env @@ -212,16 +212,6 @@ TM_DEFAULT_LOCALE=en # Underpass API URL (for project live monitoring feature) UNDERPASS_URL=https://underpass.hotosm.org -# OSM Teams -OSM_TEAMS_AUTH_DOMAIN='https://auth.mapping.team' -OSM_TEAMS_AUTH_PATH='/hyauth/oauth2/auth' -# The TOKEN domain only needs to be set if some network restriction blocks getting a token from the AUTH domain -# If it is not configured, TM will use the AUTH domain. -OSM_TEAMS_TOKEN_DOMAIN='https://auth.mapping.team' -OSM_TEAMS_TOKEN_PATH='/hyauth/oauth2/token' -OSM_TEAMS_API_URL='https://mapping.team' -# OSM_TEAMS_CLIENT_ID=foo -# OSM_TEAMS_CLIENT_SECRET=foo #EXPORT TOOL Integration with 0(Disable) and 1(Enable) and S3 URL for Export Tool #EXPORT_TOOL_S3_URL=https://foorawdataapi.s3.amazonaws.com diff --git a/frontend/.env.expand b/frontend/.env.expand index f64f07308f..58cf2498ab 100644 --- a/frontend/.env.expand +++ b/frontend/.env.expand @@ -43,10 +43,5 @@ REACT_APP_SENTRY_FRONTEND_DSN=$TM_SENTRY_FRONTEND_DSN REACT_APP_ENVIRONMENT=$TM_ENVIRONMENT REACT_APP_TM_DEFAULT_CHANGESET_COMMENT=$TM_DEFAULT_CHANGESET_COMMENT REACT_APP_RAPID_EDITOR_URL=$RAPID_EDITOR_URL -<<<<<<< HEAD -REACT_APP_OSM_TEAMS_API_URL=$OSM_TEAMS_API_URL -REACT_APP_OSM_TEAMS_CLIENT_ID=$OSM_TEAMS_CLIENT_ID -======= REACT_APP_EXPORT_TOOL_S3_URL=$EXPORT_TOOL_S3_URL REACT_APP_ENABLE_EXPORT_TOOL=$ENABLE_EXPORT_TOOL ->>>>>>> 0331beb6c7c059e04de77864c956f29eae4cafa1 diff --git a/frontend/src/components/formInputs.js b/frontend/src/components/formInputs.js index 13f035ff58..e9b7157224 100644 --- a/frontend/src/components/formInputs.js +++ b/frontend/src/components/formInputs.js @@ -9,13 +9,7 @@ import { formatCountryList } from '../utils/countries'; import { fetchLocalJSONAPI } from '../network/genericJSONRequest'; import { CheckIcon, SearchIcon, CloseIcon } from './svgIcons'; -export const RadioField = ({ - name, - value, - className, - required = false, - disabled = false, -}: Object) => ( +export const RadioField = ({ name, value, className, required = false }: Object) => ( ); diff --git a/frontend/src/components/svgIcons/index.js b/frontend/src/components/svgIcons/index.js index 76e28005cd..c828d472a3 100644 --- a/frontend/src/components/svgIcons/index.js +++ b/frontend/src/components/svgIcons/index.js @@ -82,5 +82,4 @@ export { CutIcon } from './cut'; export { FileImportIcon } from './fileImport'; export { CalendarIcon } from './calendar'; export { CommentIcon } from './comment'; -export { UserGroupIcon } from './user-group'; export { DownloadIcon } from './download'; diff --git a/frontend/src/components/svgIcons/user-group.js b/frontend/src/components/svgIcons/user-group.js deleted file mode 100644 index 1998a20ccb..0000000000 --- a/frontend/src/components/svgIcons/user-group.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -// Icon produced by FontAwesome project: https://github.com/FortAwesome/Font-Awesome/ -// License: CC-By 4.0 -export class UserGroupIcon extends React.PureComponent { - render() { - return ( - - - - ); - } -} diff --git a/frontend/src/components/teamsAndOrgs/management.js b/frontend/src/components/teamsAndOrgs/management.js index 47d073aaff..c35dd62c44 100644 --- a/frontend/src/components/teamsAndOrgs/management.js +++ b/frontend/src/components/teamsAndOrgs/management.js @@ -54,7 +54,6 @@ export function JoinMethodBox(props) { ANY: 'anyoneCanJoin', BY_REQUEST: 'byRequest', BY_INVITE: 'byInvite', - OSM_TEAMS: 'OSMTeams', }; return (
diff --git a/frontend/src/components/teamsAndOrgs/members.js b/frontend/src/components/teamsAndOrgs/members.js index 4a174bee1c..a87358bb69 100644 --- a/frontend/src/components/teamsAndOrgs/members.js +++ b/frontend/src/components/teamsAndOrgs/members.js @@ -24,7 +24,6 @@ export function Members({ setMemberJoinTeamError, managerJoinTeamError, setManagerJoinTeamError, - disableEdit, }: Object) { const token = useSelector((state) => state.auth.token); const [editMode, setEditMode] = useState(false); @@ -83,13 +82,8 @@ export function Members({

{title}

- {!disableEdit && } +
- {disableEdit && ( -
- -
- )}
{editMode && ( ( - - OSM Teams - -); - -const reSyncUsers = ({ - tmTeamId, - members, - managers, - osmTeamsId, - osmteams_token, - token, - forceUpdate, - setErrors, -}) => { - setErrors(false); - Promise.all([ - fetchExternalJSONAPI( - new URL(`/api/teams/${osmTeamsId}/members`, OSM_TEAMS_API_URL), - `Bearer ${osmteams_token}`, - 'GET', - ), - fetchExternalJSONAPI( - new URL(`/api/teams/${osmTeamsId}/moderators`, OSM_TEAMS_API_URL), - `Bearer ${osmteams_token}`, - 'GET', - ), - ]).then(([osmTeamsUsers, osmTeamsModerators]) => { - const { members: osmTeamsMembers, managers: osmTeamsManagers } = filterOSMTeamsMembers( - osmTeamsUsers.members.data, - osmTeamsModerators, - ); - const { usersAdded, usersRemoved } = getMembersDiff( - members, - osmTeamsMembers.map((user) => ({ username: user.name, function: 'MEMBER', active: true })), - false, - ); - const { usersAdded: managersAdded, usersRemoved: managersRemoved } = getMembersDiff( - managers, - osmTeamsManagers.map((user) => ({ username: user.name, function: 'MANAGER', active: true })), - true, - ); - const errors = []; - Promise.all([ - ...managersRemoved.map((user) => leaveTeamRequest(tmTeamId, user, 'MANAGER', token)), - ...usersRemoved.map((user) => leaveTeamRequest(tmTeamId, user, 'MEMBER', token)), - ...managersAdded.map((user) => - joinTeamRequest(tmTeamId, user, 'MANAGER', token).catch((e) => - errors.push({ username: user, function: 'MANAGER' }), - ), - ), - ...usersAdded.map((user) => - joinTeamRequest(tmTeamId, user, 'MEMBER', token).catch((e) => - errors.push({ username: user, function: 'MEMBER' }), - ), - ), - ]); - setErrors(errors); - forceUpdate(); - }); -}; - -export const TeamSync = ({ - osmTeamsId, - setOsmTeamsId, - setManagers, - setMembers, - managers, - members, - tmTeamId, - updateMode, - forceUpdate, - updateTeam, -}) => { - const intl = useIntl(); - let [searchParams, setSearchParams] = useSearchParams(); - const osmteams_token = useSelector((state) => state.auth.osmteams_token); - 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, - managers, - osmTeamsId, - osmteams_token, - token, - setManagers, - setMembers, - forceUpdate, - setErrors, - }; - - return ( -
-
-

- -

-
- , - }} - /> -
-
- {osmteams_token ? ( - osmTeamsId ? ( - <> - {osmTeamsId && } - {updateMode && ( - <> - - {errors && ( -
setErrors(false)} - title={intl.formatMessage(messages.dismiss)} - > - - u.username).join(', ') - : [], - number: errors.length, - }} - /> -

- -

-
-
- )} - - )} - - ) : !showSelectionModal ? ( - setShowSelectionModal(true)} - > - - - ) : ( - setShowSelectionModal(false)} - /> - ) - ) : ( - - )} - {searchParams.get('access_token') && ( - { - const newSearchParams = { ...searchParams }; - delete newSearchParams.access_token; - setSearchParams(newSearchParams); - }} - /> - )} -
- ); -}; - -const TeamBasicInfo = ({ teamId }) => { - const intl = useIntl(); - const [error, isLoading, team] = useOSMTeamInfo(teamId); - - if (teamId && error) { - return ( -
- -
- ); - } - - return ( - -
-

OSM Teams #{team.id}

-

- {team.name} - - - -

-
-
- ); -}; - -const TeamInfo = ({ members, managers, teamId, isLoadingMembers }) => { - const intl = useIntl(); - const [error, isLoading, team] = useOSMTeamInfo(teamId); - if (error) - return ( -
- -
- ); - - return ( -
-

- -

- -
-

- {team.name} - - - -

-

- {team.bio} -

-
- -
-
- {managers.map((user) => ( - - ))} -
-
- -
-
- {members.map((user) => ( - - ))} -
-
-
-
- ); -}; - -export const SelectOSMTeamsModal = ({ - osmTeamsId, - setOsmTeamsId, - setManagers, - setMembers, - tmTeamId, - updateTeam, - forceUpdate, - closeSelectionModal, -}) => { - const token = useSelector((state) => state.auth.token); - const [error, isLoading, myTeams] = useOSMTeams(); - const [selectedTeamId, setSelectedTeamId] = useState(); - const [syncStatus, setSyncStatus] = useState(); - const [teamMembersError, teamMembersIsLoading, teamMembers] = useOSMTeamUsers( - osmTeamsId || selectedTeamId, - ); - const [teamModeratorsError, teamModeratorsIsLoading, teamModerators] = useOSMTeamModerators( - osmTeamsId || selectedTeamId, - ); - const { members, managers } = filterOSMTeamsMembers( - teamMembers?.members?.data || [], - teamModerators?.length ? teamModerators : [], - ); - - const syncToExistingTeam = () => { - setSyncStatus('started'); - updateTeam(selectedTeamId); - setSyncStatus('waiting'); - const errors = []; - managers.map((user) => - joinTeamRequest(tmTeamId, user.name, 'MANAGER', token).catch((e) => - errors.push({ username: user.name, function: 'MANAGER' }), - ), - ); - members.map((user) => - joinTeamRequest(tmTeamId, user.name, 'MEMBER', token).catch((e) => - errors.push({ username: user.name, function: 'MEMBER' }), - ), - ); - forceUpdate(); - setOsmTeamsId(selectedTeamId); - }; - - const syncToNewTeam = () => { - setSyncStatus('started'); - setOsmTeamsId(selectedTeamId); - setManagers(managers.map((user) => ({ username: user.name }))); - setMembers(members.map((user) => ({ username: user.name }))); - setSyncStatus('finished'); - }; - - return ( - closeSelectionModal()}> - {(close) => ( - <> -
- {osmTeamsId || selectedTeamId ? ( -
- - {(teamMembersError || teamModeratorsError) && ( - - )} -
- ) : ( - <> -

- -

-
- {error ? ( - - ) : ( - - - - - } - > - {myTeams?.data?.map((team) => ( - - ))} - - )} -
- - )} -
-
- {(selectedTeamId || osmTeamsId) && ( - - )} -
- - {(osmTeamsId || selectedTeamId) && ( - - )} -
-
- - )} -
- ); -}; - -const OSMTeamCard = ({ team, selectTeam }) => ( -
selectTeam(team.id)} className="w-50-ns w-100 dib"> -
-
-
- - - -
-
-

{team.name}

-
-
-); - -const OSMTeamsAuthButton = () => { - const location = useLocation(); - const [debouncedCreateLoginWindow] = useDebouncedCallback( - (redirectTo) => createOSMTeamsLoginWindow(redirectTo), - 3000, - { leading: true }, - ); - - return ( - <> -

- -

- - - ); -}; - -const SuccessfulAuthenticationModal = ({ onCloseFn }) => { - return ( - onCloseFn()}> - {(close) => ( -
-

- -

-

- -

- -
- )} -
- ); -}; diff --git a/frontend/src/components/teamsAndOrgs/teams.js b/frontend/src/components/teamsAndOrgs/teams.js index d6170023d6..a20ef59578 100644 --- a/frontend/src/components/teamsAndOrgs/teams.js +++ b/frontend/src/components/teamsAndOrgs/teams.js @@ -7,17 +7,14 @@ import { Form, Field, useFormState } from 'react-final-form'; import ReactTooltip from 'react-tooltip'; import messages from './messages'; -import { ExternalLinkIcon, InfoIcon } from '../svgIcons'; +import { InfoIcon } from '../svgIcons'; import { useEditTeamAllowed } from '../../hooks/UsePermissions'; import { UserAvatar, UserAvatarList } from '../user/avatar'; import { AddButton, ViewAllLink, Management, VisibilityBox, JoinMethodBox } from './management'; import { RadioField, OrganisationSelectInput, TextField } from '../formInputs'; -import { Button, CustomButton, EditButton } from '../button'; +import { Button, EditButton } from '../button'; import { nCardPlaceholders } from './teamsPlaceholder'; -import { OSM_TEAMS_API_URL } from '../../config'; import { Alert } from '../alert'; -import Popup from 'reactjs-popup'; -import { LeaveTeamConfirmationAlert } from './leaveTeamConfirmationAlert'; export function TeamsManagement({ teams, @@ -172,7 +169,7 @@ export function TeamCard({ team }: Object) { ); } -export function TeamInformation({ disableJoinMethodField }) { +export function TeamInformation(props) { const intl = useIntl(); const labelClasses = 'db pt3 pb2'; const fieldClasses = 'blue-grey w-100 pv3 ph2 input-reset ba b--grey-light bg-transparent'; @@ -181,7 +178,6 @@ export function TeamInformation({ disableJoinMethodField }) { ANY: 'anyoneCanJoin', BY_REQUEST: 'byRequest', BY_INVITE: 'byInvite', - OSM_TEAMS: 'OSMTeams', }; return ( @@ -210,12 +206,7 @@ export function TeamInformation({ disableJoinMethodField }) { {Object.keys(joinMethods).map((method) => (
- + @@ -229,7 +220,7 @@ export function TeamInformation({ disableJoinMethodField }) {
))}
- {['BY_INVITE', 'OSM_TEAMS'].includes(formState.values.joinMethod) && ( + {formState.values.joinMethod === 'BY_INVITE' && (
@@ -430,23 +421,6 @@ export function TeamSideBar({ team, members, managers, requestedToJoin }: Object )}
- {team.osm_teams_id && ( - - {' '} - - - - - - )}
@@ -494,59 +468,3 @@ export const TeamBox = ({ team, className }: Object) => ( ); - -export const TeamDetailPageFooter = ({ team, isMember, joinTeamFn, leaveTeamFn }) => { - return ( -
-
- - - - - -
-
- {isMember ? ( - - - - } - modal - closeOnEscape - > - {(close) => ( - - )} - - ) : ( - team.joinMethod !== 'BY_INVITE' && ( - joinTeamFn()} - disabled={team.joinMethod === 'OSM_TEAMS'} - > - - - ) - )} -
-
- ); -}; diff --git a/frontend/src/components/teamsAndOrgs/tests/teams.test.js b/frontend/src/components/teamsAndOrgs/tests/teams.test.js index 768f385828..4ef5a6e41d 100644 --- a/frontend/src/components/teamsAndOrgs/tests/teams.test.js +++ b/frontend/src/components/teamsAndOrgs/tests/teams.test.js @@ -3,7 +3,6 @@ import TestRenderer from 'react-test-renderer'; import { render, screen, waitFor, act } from '@testing-library/react'; import { FormattedMessage } from 'react-intl'; import { MemoryRouter } from 'react-router-dom'; -import userEvent from '@testing-library/user-event'; import { createComponentWithIntl, @@ -12,7 +11,7 @@ import { renderWithRouter, createComponentWithMemoryRouter, } from '../../../utils/testWithIntl'; -import { TeamBox, TeamsBoxList, TeamsManagement, Teams, TeamCard, TeamSideBar, TeamDetailPageFooter } from '../teams'; +import { TeamBox, TeamsBoxList, TeamsManagement, Teams, TeamCard, TeamSideBar } from '../teams'; import { store } from '../../../store'; import { teams, team } from '../../../network/tests/mockData/teams'; @@ -402,149 +401,4 @@ describe('TeamSideBar component', () => { }), ).not.toBeInTheDocument(); }); - - it('when OSM Teams sync is enabled, it should show a message', () => { - const teamWithOSMTeams = {...team}; - teamWithOSMTeams.osm_teams_id = 1234; - teamWithOSMTeams.joinMethod = 'OSM_TEAMS'; - renderWithRouter( - - - , - ); - - expect( - screen.getByText( - 'The members and managers of this team are configured through the OSM Teams platform.' - ) - ).toBeInTheDocument(); - expect( - screen.getByText('Open on OSM Teams').href.endsWith('/teams/1234') - ).toBeTruthy(); - }); }); - - -describe('TeamDetailPageFooter component', () => { - const joinTeamFn = jest.fn(); - const leaveTeamFn = jest.fn(); - - it('has Join team button enabled for ANY joinMethod if user is not member', async () => { - renderWithRouter( - - - - ); - expect(screen.getByText('Join team').disabled).toBeFalsy(); - await userEvent.click(screen.getByText('Join team')); - expect(joinTeamFn).toHaveBeenCalledTimes(1); - expect( - screen.getByRole('link').href.endsWith('/contributions/teams') - ).toBeTruthy(); - }); - - it('has Leave team button enabled for ANY joinMethod if user is a member', async () => { - renderWithRouter( - - - - ); - expect(screen.getByText('Leave team').disabled).toBeFalsy(); - await userEvent.click(screen.getByText('Leave team')); - await userEvent.click(screen.getByText('Leave')); - expect(leaveTeamFn).toHaveBeenCalledTimes(1); - expect( - screen.getByRole('link').href.endsWith('/contributions/teams') - ).toBeTruthy(); - }); - - it('has Join team button enabled for BY_REQUEST joinMethod if user is not member', async () => { - renderWithRouter( - - - - ); - expect(screen.getByText('Join team').disabled).toBeFalsy(); - await userEvent.click(screen.getByText('Join team')); - expect(joinTeamFn).toHaveBeenCalledTimes(1); - }); - - it('has Leave team button enabled for BY_REQUEST joinMethod if user is a member', async () => { - renderWithRouter( - - - - ); - expect(screen.getByText('Leave team').disabled).toBeFalsy(); - await userEvent.click(screen.getByText('Leave team')); - await userEvent.click(screen.getByText('Leave')); - expect(leaveTeamFn).toHaveBeenCalledTimes(1); - }); - - it('has Leave team button enabled for BY_INVITE joinMethod if user is a member', async () => { - renderWithRouter( - - - - ); - expect(screen.getByText('Leave team').disabled).toBeFalsy(); - await userEvent.click(screen.getByText('Leave team')); - await userEvent.click(screen.getByText('Leave')); - expect(leaveTeamFn).toHaveBeenCalledTimes(1); - }); - - it('has Join team button disabled for OSM_TEAMS joinMethod if user is not a member', async () => { - renderWithRouter( - - - - ); - expect(screen.getByText('Join team').disabled).toBeTruthy(); - await userEvent.click(screen.getByText('Join team')); - expect(joinTeamFn).toHaveBeenCalledTimes(0); - }); - - it('has Leave team button disabled for OSM_TEAMS joinMethod if user is a member', async () => { - renderWithRouter( - - - - ); - expect(screen.getByText('Leave team').disabled).toBeTruthy(); - }); -}); \ No newline at end of file diff --git a/frontend/src/config/index.js b/frontend/src/config/index.js index 0709a2f9d1..7074b6e2cd 100644 --- a/frontend/src/config/index.js +++ b/frontend/src/config/index.js @@ -63,10 +63,6 @@ export const RAPID_EDITOR_URL = export const EXPORT_TOOL_S3_URL = process.env.REACT_APP_EXPORT_TOOL_S3_URL || ''; export const ENABLE_EXPORT_TOOL = process.env.REACT_APP_ENABLE_EXPORT_TOOL || ''; -// OSM Teams integration -export const OSM_TEAMS_API_URL = process.env.REACT_APP_OSM_TEAMS_API_URL || 'https://mapping.team'; -export const OSM_TEAMS_CLIENT_ID = process.env.REACT_APP_OSM_TEAMS_CLIENT_ID || ''; - export const TASK_COLOURS = { READY: '#fff', LOCKED_FOR_MAPPING: '#fff', diff --git a/frontend/src/hooks/UseOSMTeams.js b/frontend/src/hooks/UseOSMTeams.js deleted file mode 100644 index 7bdaa1d656..0000000000 --- a/frontend/src/hooks/UseOSMTeams.js +++ /dev/null @@ -1,53 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; - -import { OSM_TEAMS_API_URL } from '../config'; -import { fetchExternalJSONAPI } from '../network/genericJSONRequest'; - -const useFetchExternal = (url, trigger = true, token) => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(true); - const [data, setData] = useState({}); - - useEffect(() => { - (async () => { - if (trigger) { - setLoading(true); - try { - // replace in locale is needed because the backend uses underscore instead of dash - const response = await fetchExternalJSONAPI(url, token, 'GET'); - setData(response); - setLoading(false); - } catch (e) { - setError(e); - setLoading(false); - } - } - })(); - }, [url, token, trigger]); - return [error, loading, data]; -}; - -export const useOSMTeams = () => { - const osmTeamsToken = useSelector((state) => state.auth.osmteams_token); - const myTeamsURL = new URL('/api/my/teams', OSM_TEAMS_API_URL); - return useFetchExternal(myTeamsURL.href, osmTeamsToken, `Bearer ${osmTeamsToken}`); -}; - -export const useOSMTeamUsers = (teamId) => { - const osmTeamsToken = useSelector((state) => state.auth.osmteams_token); - const myTeamsURL = new URL(`/api/teams/${teamId}/members`, OSM_TEAMS_API_URL); - return useFetchExternal(myTeamsURL.href, Boolean(teamId), `Bearer ${osmTeamsToken}`); -}; - -export const useOSMTeamModerators = (teamId) => { - const osmTeamsToken = useSelector((state) => state.auth.osmteams_token); - const myTeamsURL = new URL(`/api/teams/${teamId}/moderators`, OSM_TEAMS_API_URL); - return useFetchExternal(myTeamsURL.href, Boolean(teamId), `Bearer ${osmTeamsToken}`); -}; - -export const useOSMTeamInfo = (teamId) => { - const osmTeamsToken = useSelector((state) => state.auth.osmteams_token); - const myTeamsURL = new URL(`/api/teams/${teamId}`, OSM_TEAMS_API_URL); - return useFetchExternal(myTeamsURL.href, Boolean(teamId), `Bearer ${osmTeamsToken}`); -}; diff --git a/frontend/src/network/tests/mockData/osmTeams.js b/frontend/src/network/tests/mockData/osmTeams.js deleted file mode 100644 index 561f7013a1..0000000000 --- a/frontend/src/network/tests/mockData/osmTeams.js +++ /dev/null @@ -1,79 +0,0 @@ -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 }, -]; diff --git a/frontend/src/network/tests/mockData/teams.js b/frontend/src/network/tests/mockData/teams.js index 2559a8c23f..f8aaf1fbb8 100644 --- a/frontend/src/network/tests/mockData/teams.js +++ b/frontend/src/network/tests/mockData/teams.js @@ -51,7 +51,7 @@ export const teamCreationSuccess = { teamId: 123, }; -export const teamUpdateSuccess = { +export const teamUpdationSuccess = { Status: 'Updated', }; diff --git a/frontend/src/network/tests/server-handlers.js b/frontend/src/network/tests/server-handlers.js index b21af324e1..66851658df 100644 --- a/frontend/src/network/tests/server-handlers.js +++ b/frontend/src/network/tests/server-handlers.js @@ -56,7 +56,7 @@ import { teams, team, teamCreationSuccess, - teamUpdateSuccess, + teamUpdationSuccess, teamDeletionSuccess, } from './mockData/teams'; import { userTasks } from './mockData/tasksStats'; @@ -69,7 +69,7 @@ import { ohsomeNowMetadata, } from './mockData/miscellaneous'; import tasksGeojson from '../../utils/tests/snippets/tasksGeometry'; -import { API_URL, OSM_TEAMS_API_URL, OHSOME_STATS_BASE_URL } from '../../config'; +import { API_URL, OHSOME_STATS_BASE_URL } from '../../config'; import { notifications, ownCountUnread } from './mockData/notifications'; import { authLogin, setUser, userRegister } from './mockData/auth'; import { @@ -81,7 +81,6 @@ 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) => { @@ -253,7 +252,7 @@ const handlers = [ return res(ctx.json(teamCreationSuccess)); }), rest.patch(API_URL + 'teams/:id/', (req, res, ctx) => { - return res(ctx.json(teamUpdateSuccess)); + return res(ctx.json(teamUpdationSuccess)); }), rest.delete(API_URL + 'teams/:id', (req, res, ctx) => { return res(ctx.json(teamDeletionSuccess)); @@ -365,19 +364,6 @@ 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) => { diff --git a/frontend/src/routes.js b/frontend/src/routes.js index fe534c0d6e..d963246247 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -1,7 +1,7 @@ import { createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom'; import { Root } from './views/root'; -import { Authorized, OSMTeamsAuthorized } from './views/authorized'; +import { Authorized } from './views/authorized'; import { NotFound } from './views/notFound'; import { FallbackComponent } from './views/fallback'; import { Redirect } from './components/redirect'; @@ -45,7 +45,7 @@ export const router = createBrowserRouter( path="projects/:id" lazy={async () => { const { ProjectDetailPage } = await import( - './views/project' /* webpackChunkName: "projectDetail" */ + './views/project' /* webpackChunkName: "project" */ ); return { Component: ProjectDetailPage }; }} @@ -54,7 +54,7 @@ export const router = createBrowserRouter( path="projects/:id/tasks" lazy={async () => { const { SelectTask } = await import( - './views/taskSelection' /* webpackChunkName: "projectDetail" */ + './views/taskSelection' /* webpackChunkName: "taskSelection" */ ); return { Component: SelectTask }; }} @@ -212,7 +212,6 @@ export const router = createBrowserRouter( }} /> } /> - } /> { @@ -363,7 +362,7 @@ export const router = createBrowserRouter( path="projects/:id" lazy={async () => { const { ProjectEdit } = await import( - './views/projectEdit' /* webpackChunkName: "project" */ + './views/projectEdit' /* webpackChunkName: "projectEdit" */ ); return { Component: ProjectEdit }; }} diff --git a/frontend/src/store/actions/auth.js b/frontend/src/store/actions/auth.js index 5916561e5e..75556a10d3 100644 --- a/frontend/src/store/actions/auth.js +++ b/frontend/src/store/actions/auth.js @@ -11,7 +11,6 @@ export const types = { UPDATE_OSM_INFO: 'UPDATE_OSM_INFO', GET_USER_DETAILS: 'GET_USER_DETAILS', SET_TOKEN: 'SET_TOKEN', - SET_OSM_TEAMS_TOKEN: 'SET_OSM_TEAMS_TOKEN', SET_SESSION: 'SET_SESSION', CLEAR_SESSION: 'CLEAR_SESSION', }; @@ -44,8 +43,6 @@ export const logout = () => (dispatch) => { safeStorage.removeItem('token'); safeStorage.removeItem('action'); safeStorage.removeItem('osm_oauth_token'); - safeStorage.removeItem('osmteams_token'); - safeStorage.removeItem('osmteams_refresh_token'); safeStorage.removeItem('tasksSortOrder'); dispatch(clearUserDetails()); }; @@ -85,13 +82,6 @@ export function updateToken(token) { }; } -export function updateOSMTeamsToken(osmteams_token) { - return { - type: types.SET_OSM_TEAMS_TOKEN, - osmteams_token: osmteams_token, - }; -} - export function updateSession(session) { return { type: types.SET_SESSION, @@ -113,12 +103,6 @@ export const setAuthDetails = (username, token, osm_oauth_token) => (dispatch) = dispatch(setUserDetails(username, encoded_token)); }; -export const setOSMTeamsDetails = (osmteams_token, refresh_token) => (dispatch) => { - safeStorage.setItem('osmteams_token', osmteams_token); - safeStorage.setItem('osmteams_refresh_token', refresh_token); - dispatch(updateOSMTeamsToken(osmteams_token)); -}; - // UPDATES OSM INFORMATION OF THE USER export const setUserDetails = (username, encodedToken, update = false) => diff --git a/frontend/src/store/reducers/auth.js b/frontend/src/store/reducers/auth.js index 2a21d7a481..30656f6b40 100644 --- a/frontend/src/store/reducers/auth.js +++ b/frontend/src/store/reducers/auth.js @@ -3,7 +3,6 @@ import { types } from '../actions/auth'; const initialState = { userDetails: {}, token: '', - osmteams_token: '', session: {}, osm: {}, organisations: [], @@ -27,9 +26,6 @@ export function authorizationReducer(state = initialState, action) { case types.SET_TOKEN: { return { ...state, token: action.token }; } - case types.SET_OSM_TEAMS_TOKEN: { - return { ...state, osmteams_token: action.osmteams_token }; - } case types.SET_SESSION: { return { ...state, session: action.session }; } diff --git a/frontend/src/utils/login.js b/frontend/src/utils/login.js index ee77c6cf4b..0c674c0929 100644 --- a/frontend/src/utils/login.js +++ b/frontend/src/utils/login.js @@ -54,30 +54,3 @@ export const createLoginWindow = (redirectTo) => { }; }); }; - -export const createOSMTeamsLoginWindow = (redirectTo) => { - const popup = createPopup('OSM auth', ''); - let url = `system/osm-teams-authentication/login/`; - fetchLocalJSONAPI(url).then((resp) => { - popup.location = resp.auth_url; - // Perform token exchange. - - window.authComplete = (authCode, state) => { - let callback_url = `system/osm-teams-authentication/callback/?code=${authCode}`; - - if (resp.state === state) { - fetchLocalJSONAPI(callback_url).then((res) => { - const params = new URLSearchParams({ - access_token: res.access_token, - refresh_token: res.refresh_token, - redirect_to: redirectTo, - }).toString(); - let redirectUrl = `/osmteams-authorized/?${params}`; - window.location.href = redirectUrl; - }); - } else { - throw new Error('States do not match'); - } - }; - }); -}; diff --git a/frontend/src/utils/teamMembersDiff.js b/frontend/src/utils/teamMembersDiff.js index dff5c3a8ff..0873ae3c96 100644 --- a/frontend/src/utils/teamMembersDiff.js +++ b/frontend/src/utils/teamMembersDiff.js @@ -35,10 +35,3 @@ export function formatMemberObject(user, manager = false) { active: true, }; } - -export const filterOSMTeamsMembers = (members, moderators) => { - const managersIds = moderators.map((user) => user.osm_id); - const managers = members.filter((user) => managersIds.includes(user.id)); - members = members.filter((user) => !managersIds.includes(user.id)); - return { managers, members }; -} \ No newline at end of file diff --git a/frontend/src/views/authorized.js b/frontend/src/views/authorized.js index fd6ec2fab8..6e474ed673 100644 --- a/frontend/src/views/authorized.js +++ b/frontend/src/views/authorized.js @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { useDispatch } from 'react-redux'; -import { setAuthDetails, setOSMTeamsDetails } from '../store/actions/auth'; +import { setAuthDetails } from '../store/actions/auth'; export function Authorized(props) { const navigate = useNavigate(); @@ -32,32 +32,3 @@ export function Authorized(props) { return <>{!isReadyToRedirect ? null :
redirecting
}; } - -export function OSMTeamsAuthorized(props) { - const navigate = useNavigate(); - const location = useLocation(); - const dispatch = useDispatch(); - const [isReadyToRedirect, setIsReadyToRedirect] = useState(false); - - useEffect(() => { - const params = new URLSearchParams(location.search); - let authCode = params.get('code'); - let state = params.get('state'); - if (authCode !== null) { - window.opener.authComplete(authCode, state); - window.close(); - return; - } - const sessionToken = params.get('access_token'); - const refreshToken = params.get('refresh_token'); - dispatch(setOSMTeamsDetails(sessionToken, refreshToken)); - setIsReadyToRedirect(true); - const redirectUrl = - params.get('redirect_to') - ? `${params.get('redirect_to')}?access_token=${params.get('access_token')}` - : '/manage/teams'; - navigate(redirectUrl); - }, [dispatch, location.search, navigate]); - - return <>{isReadyToRedirect ? null :
redirecting
}; -} diff --git a/frontend/src/views/messages.js b/frontend/src/views/messages.js index 892c240346..4bfe23c3ef 100644 --- a/frontend/src/views/messages.js +++ b/frontend/src/views/messages.js @@ -145,6 +145,22 @@ export default defineMessages({ id: 'teamsAndOrgs.management.campaign.button.create', defaultMessage: 'Create campaign', }, + myTeams: { + id: 'teamsAndOrgs.management.button.my_teams', + defaultMessage: 'My teams', + }, + joinTeam: { + id: 'teamsAndOrgs.management.button.join_team', + defaultMessage: 'Join team', + }, + cancelRequest: { + id: 'teamsAndOrgs.management.button.cancel_request', + defaultMessage: 'Cancel request', + }, + leaveTeam: { + id: 'teamsAndOrgs.management.button.leave_team', + defaultMessage: 'Leave team', + }, cancel: { id: 'teamsAndOrgs.management.button.cancel', defaultMessage: 'Cancel', diff --git a/frontend/src/views/teams.js b/frontend/src/views/teams.js index 19dfc90649..a11ac3f254 100644 --- a/frontend/src/views/teams.js +++ b/frontend/src/views/teams.js @@ -11,9 +11,9 @@ import { withDefault, } from 'use-query-params'; import toast from 'react-hot-toast'; +import Popup from 'reactjs-popup'; import messages from './messages'; -import { OSM_TEAMS_CLIENT_ID } from '../config'; import { useFetch } from '../hooks/UseFetch'; import { useEditTeamAllowed } from '../hooks/UsePermissions'; import { useSetTitleTag } from '../hooks/UseMetaTags'; @@ -33,21 +33,18 @@ import { TeamForm, TeamsManagement, TeamSideBar, - TeamDetailPageFooter, } from '../components/teamsAndOrgs/teams'; import { MessageMembers } from '../components/teamsAndOrgs/messageMembers'; import { Projects } from '../components/teamsAndOrgs/projects'; +import { LeaveTeamConfirmationAlert } from '../components/teamsAndOrgs/leaveTeamConfirmationAlert'; import { FormSubmitButton, CustomButton } from '../components/button'; import { DeleteModal } from '../components/deleteModal'; import { NotFound } from './notFound'; import { PaginatorLine } from '../components/paginator'; import { updateEntity } from '../utils/management'; import { EntityError } from '../components/alert'; -import { TeamSync } from '../components/teamsAndOrgs/teamSync'; import { useTeamsQuery } from '../api/teams'; -const ENABLE_OSM_TEAMS_INTEGRATION = Boolean(OSM_TEAMS_CLIENT_ID); - export function ManageTeams() { useSetTitleTag('Manage teams'); return ; @@ -112,7 +109,7 @@ export function ListTeams({ managementView = false }: Object) { ); } -export const joinTeamRequest = (team_id, username, role, token) => { +const joinTeamRequest = (team_id, username, role, token) => { return pushToLocalJSONAPI( `teams/${team_id}/actions/add/`, JSON.stringify({ username: username, role: role }), @@ -121,7 +118,7 @@ export const joinTeamRequest = (team_id, username, role, token) => { ); }; -export const leaveTeamRequest = (team_id, username, role, token) => { +const leaveTeamRequest = (team_id, username, role, token) => { return pushToLocalJSONAPI( `teams/${team_id}/actions/leave/`, JSON.stringify({ username: username, role: role }), @@ -143,46 +140,25 @@ export function CreateTeam() { } = useModifyMembers([{ username: userDetails.username, pictureUrl: userDetails.pictureUrl }]); const { members, setMembers, addMember, removeMember } = useModifyMembers([]); const [isError, setIsError] = useState(false); - const [osmTeamsId, setOsmTeamsId] = useState(); - - useEffect(() => { - if (userDetails && userDetails.username && managers.length === 0) { - setManagers([{ username: userDetails.username, pictureUrl: userDetails.pictureUrl }]); - } - }, [userDetails, managers, setManagers]); const createTeam = (payload) => { delete payload['organisation']; setIsError(false); - payload.osm_teams_id = osmTeamsId; pushToLocalJSONAPI('teams/', JSON.stringify(payload), token, 'POST') .then((result) => { - const errors = []; - Promise.all([ - ...managers - .filter((user) => user.username !== userDetails.username) - .map((user) => - joinTeamRequest(result.teamId, user.username, 'MANAGER', token).catch((e) => - errors.push({ username: user.username, function: 'MANAGER' }), - ), - ), - ...members.map((user) => - joinTeamRequest(result.teamId, user.username, 'MEMBER', token).catch((e) => - errors.push({ username: user.username, function: 'MEMBER' }), - ), - ), - ]).then(() => { - const additionalSearchParam = errors.length ? `?syncUsersErrors=${errors.length}` : ''; - toast.success( - , - ); - navigate(`/manage/teams/${result.teamId}${additionalSearchParam}`); - }); + managers + .filter((user) => user.username !== userDetails.username) + .map((user) => joinTeamRequest(result.teamId, user.username, 'MANAGER', token)); + members.map((user) => joinTeamRequest(result.teamId, user.username, 'MEMBER', token)); + toast.success( + , + ); + navigate(`/manage/teams/${result.teamId}`); }) .catch(() => setIsError(true)); }; @@ -192,7 +168,6 @@ export function CreateTeam() { onSubmit={(values) => createTeam(values)} initialValues={{ visibility: 'PUBLIC' }} render={({ handleSubmit, pristine, submitting, values }) => { - if (osmTeamsId) values.joinMethod = 'OSM_TEAMS'; return (
@@ -204,7 +179,7 @@ export function CreateTeam() {

- +
{isError && } @@ -215,7 +190,7 @@ export function CreateTeam() { removeMembers={removeManager} members={managers} resetMembersFn={setManagers} - disableEdit={osmTeamsId} + creationMode={true} />
@@ -224,20 +199,10 @@ export function CreateTeam() { removeMembers={removeMember} members={members} resetMembersFn={setMembers} - disableEdit={osmTeamsId} + creationMode={true} type={'members'} />
- {ENABLE_OSM_TEAMS_INTEGRATION && ( -
- -
- )}
@@ -265,7 +230,7 @@ export function CreateTeam() { ); } -export function EditTeam() { +export function EditTeam(props) { const { id } = useParams(); const userDetails = useSelector((state) => state.auth.userDetails); const token = useSelector((state) => state.auth.token); @@ -279,15 +244,12 @@ export function EditTeam() { const [memberJoinTeamError, setMemberJoinTeamError] = useState(null); const [managerJoinTeamError, setManagerJoinTeamError] = useState(null); const [isError, setIsError] = useState(false); - const [osmTeamsId, setOsmTeamsId] = useState(); - useSetTitleTag(`Edit ${team.name}`); useEffect(() => { if (!initManagers && team && team.members) { setManagers(filterActiveManagers(team.members)); setMembers(filterActiveMembers(team.members)); setRequests(filterInactiveMembersAndManagers(team.members)); - setOsmTeamsId(team.osm_teams_id); setInitManagers(true); } }, [team, managers, initManagers]); @@ -299,6 +261,8 @@ export function EditTeam() { } }, [team]); + useSetTitleTag(`Edit ${team.name}`); + const addManagers = (values) => { const newValues = values .filter((newUser) => !managers.map((i) => i.username).includes(newUser.username)) @@ -374,13 +338,9 @@ export function EditTeam() { const onUpdateTeamFailure = () => setIsError(true); const updateTeam = (payload) => { - if (['ANY', 'BY_REQUEST'].includes(payload.joinMethod)) { + if (payload.joinMethod !== 'BY_INVITE') { payload.visibility = 'PUBLIC'; } - payload.osm_teams_id = osmTeamsId; - // 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); }; @@ -413,7 +373,6 @@ export function EditTeam() { joinMethod: team.joinMethod, visibility: team.visibility, organisation_id: team.organisation_id, - osm_teams_id: team.osm_teams_id, }} updateTeam={updateTeam} disabledForm={error || loading} @@ -429,7 +388,6 @@ export function EditTeam() { members={managers} managerJoinTeamError={managerJoinTeamError} setManagerJoinTeamError={setManagerJoinTeamError} - disableEdit={osmTeamsId} />
- {ENABLE_OSM_TEAMS_INTEGRATION && - (osmTeamsId || (members.length < 1 && managers.length < 2)) && ( -
- - pushToLocalJSONAPI( - `teams/${id}/`, - JSON.stringify({ osm_teams_id: selectedTeamId, joinMethod: 'OSM_TEAMS' }), - token, - 'PATCH', - ) - } - /> -
- )} -
- +
+
+ + + + + +
+
+ {isMember ? ( + + + + } + modal + closeOnEscape + > + {(close) => ( + + )} + + ) : ( + team.joinMethod !== 'BY_INVITE' && ( + joinTeam()} + > + + + ) + )} +
+
); } diff --git a/frontend/src/views/tests/teams.test.js b/frontend/src/views/tests/teams.test.js index 28a6b20618..c83c26c17c 100644 --- a/frontend/src/views/tests/teams.test.js +++ b/frontend/src/views/tests/teams.test.js @@ -2,7 +2,6 @@ import '@testing-library/jest-dom'; import { screen, waitFor, within, act } from '@testing-library/react'; import { ReactRouter6Adapter } from 'use-query-params/adapters/react-router-6'; import { QueryParamProvider } from 'use-query-params'; -import userEvent from '@testing-library/user-event'; import toast from 'react-hot-toast'; import { @@ -14,19 +13,12 @@ import { import { ManageTeams, EditTeam, CreateTeam, MyTeams } from '../teams'; import { store } from '../../store'; import { setupFaultyHandlers } from '../../network/tests/server'; -import * as config from '../../config'; jest.mock('react-hot-toast', () => ({ success: jest.fn(), error: jest.fn(), })); -// Set OSM_TEAMS_CLIENT_ID to be able to test OSM Teams integration components -Object.defineProperty(config, 'OSM_TEAMS_CLIENT_ID', { - value: () => '123abc', - writable: true, -}); - describe('List Teams', () => { it('should show loading placeholder when teams are being fetched', async () => { const { container } = renderWithRouter( @@ -134,79 +126,6 @@ describe('Create Team', () => { }); }); -describe('Create Team with OSM Teams integration', () => { - it('Sync with OSM Teams section is present', () => { - renderWithRouter( - - - , - ); - act(() => { - store.dispatch({ - type: 'SET_USER_DETAILS', - userDetails: { id: 122, username: 'test_user', role: 'ADMIN' }, - }); - store.dispatch({ type: 'SET_TOKEN', token: 'validToken' }); - }); - expect(screen.getByText('Sync with OSM Teams')).toBeInTheDocument(); - expect(screen.getByText('Authenticate OSM Teams')).toBeInTheDocument(); - }); - it('Setting osmteams_token enables team selection', async () => { - renderWithRouter( - - - , - ); - act(() => { - store.dispatch({ - type: 'SET_USER_DETAILS', - userDetails: { id: 122, username: 'test_user', role: 'ADMIN' }, - }); - store.dispatch({ type: 'SET_TOKEN', token: 'validToken' }); - store.dispatch({ - type: 'SET_OSM_TEAMS_TOKEN', - osmteams_token: 'abc123', - }); - }); - expect(screen.queryByText('Authenticate OSM Teams')).not.toBeInTheDocument(); - expect(screen.getByText('Select a team from OSM Teams')).toBeInTheDocument(); - // Open OSM Teams selection modal - await userEvent.click(screen.getByText('Select a team from OSM Teams')); - await waitFor(() => expect(screen.getByText('OSM Teams Developers')).toBeInTheDocument()); - expect(screen.getByText('SOTMUS 2023')).toBeInTheDocument(); - expect(screen.getByText('My Friends')).toBeInTheDocument(); - expect(screen.getAllByText('Cancel').length).toBe(2); - // Select a team - await userEvent.click(screen.getByText('OSM Teams Developers')); - await waitFor(() => expect(screen.getByTitle('geohacker')).toBeInTheDocument()); - expect(screen.getByText('Selected team')).toBeInTheDocument(); - expect(screen.getByText('Back')).toBeInTheDocument(); - expect(screen.getAllByText('Managers').length).toBe(2); - expect(screen.getAllByText('Members').length).toBe(2); - await userEvent.click(screen.getByText('Confirm selection')); - // Modal closes - await waitFor(() => - expect(screen.getByTitle('Open on OSM Teams').href.endsWith('/teams/1')).toBeTruthy() - ); - expect(screen.queryByText('Selected team')).not.toBeInTheDocument(); - expect(screen.getByTitle('kamicut')).toBeInTheDocument(); - expect(screen.getByTitle('geohacker')).toBeInTheDocument(); - expect(screen.getByTitle('wille')).toBeInTheDocument(); - expect(screen.getByTitle('sanjayb')).toBeInTheDocument(); - expect(screen.getByText('OSM Teams Developers')).toBeInTheDocument(); - const radios = screen.getAllByRole('radio'); - expect(radios.length).toBe(6); - // OSM Teams join method is checked and disabled - expect(radios.slice(0, 2).filter((i) => i.checked)).toEqual([]); - expect(radios[3].checked).toBeTruthy(); - expect(radios[3].disabled).toBeTruthy(); - // Privacy options are enabled and public is selected - expect(radios[4].checked).toBeTruthy(); - expect(radios[4].disabled).toBeFalsy(); - expect(radios[5].disabled).toBeFalsy(); - }); -}); - describe('Edit Team', () => { it('should display default details of the team before editing', async () => { renderWithRouter( @@ -281,49 +200,6 @@ describe('Edit Team', () => { ).toBeInTheDocument(), ); }); - - it('Enable OSM Teams sync', async () => { - renderWithRouter( - - - , - ); - act(() => { - store.dispatch({ - type: 'SET_USER_DETAILS', - userDetails: { id: 122, username: 'test_user', role: 'ADMIN' }, - }); - store.dispatch({ type: 'SET_TOKEN', token: 'validToken' }); - store.dispatch({ - type: 'SET_OSM_TEAMS_TOKEN', - osmteams_token: 'abc123', - }); - }); - // Open OSM Teams selection modal - await userEvent.click(screen.getByText('Select a team from OSM Teams')); - await waitFor(() => expect(screen.getByText('OSM Teams Developers')).toBeInTheDocument()); - expect(screen.getByText('SOTMUS 2023')).toBeInTheDocument(); - expect(screen.getByText('My Friends')).toBeInTheDocument(); - expect(screen.getByText('Cancel')).toBeInTheDocument(); - // Select a team - await userEvent.click(screen.getByText('OSM Teams Developers')); - await waitFor(() => expect(screen.getByTitle('geohacker')).toBeInTheDocument()); - expect(screen.getByText('Selected team')).toBeInTheDocument(); - expect(screen.getByText('Back')).toBeInTheDocument(); - expect(screen.getAllByText('Managers').length).toBe(2); - expect(screen.getAllByText('Members').length).toBe(2); - await userEvent.click(screen.getByText('Confirm selection')); - // Modal closes - await waitFor(() => - expect(screen.getByTitle('Open on OSM Teams').href.endsWith('/teams/1')).toBeTruthy() - ); - expect(screen.queryByText('Selected team')).not.toBeInTheDocument(); - expect(screen.getByTitle('kamicut')).toBeInTheDocument(); - expect(screen.getByTitle('geohacker')).toBeInTheDocument(); - expect(screen.getByTitle('wille')).toBeInTheDocument(); - expect(screen.getByTitle('sanjayb')).toBeInTheDocument(); - expect(screen.getByText('OSM Teams Developers')).toBeInTheDocument(); - }); }); describe('Delete Team', () => { diff --git a/migrations/versions/52a67f6cef20_.py b/migrations/versions/52a67f6cef20_.py deleted file mode 100644 index b685ba0e80..0000000000 --- a/migrations/versions/52a67f6cef20_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""empty message - -Revision ID: 52a67f6cef20 -Revises: 42c45e74752b -Create Date: 2023-07-06 12:26:39.420411 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = "52a67f6cef20" -down_revision = "42c45e74752b" -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column("teams", sa.Column("osm_teams_id", sa.BigInteger(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column("teams", "osm_teams_id") - # ### end Alembic commands ### diff --git a/tests/backend/integration/api/teams/test_resources.py b/tests/backend/integration/api/teams/test_resources.py index 9808e5e787..5ff3baeaa5 100644 --- a/tests/backend/integration/api/teams/test_resources.py +++ b/tests/backend/integration/api/teams/test_resources.py @@ -259,7 +259,6 @@ def test_get_teams_authorised_passes(self): "organisationId": 23, "description": None, "joinMethod": "ANY", - "osm_teams_id": None, "logo": None, "managersCount": 0, "members": [],