Skip to content

Commit

Permalink
Enable selecting sandbox when creating a project
Browse files Browse the repository at this point in the history
  • Loading branch information
quincylvania committed Nov 19, 2024
1 parent 062e141 commit cb54788
Show file tree
Hide file tree
Showing 18 changed files with 100 additions and 61 deletions.
2 changes: 1 addition & 1 deletion backend/api/projects/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def patch(self, project_id):
properties:
projectDatabase:
type: string
default: OSM
default:
projectStatus:
type: string
default: DRAFT
Expand Down
21 changes: 1 addition & 20 deletions backend/models/dtos/project_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from backend.models.dtos.team_dto import ProjectTeamDTO
from backend.models.dtos.interests_dto import InterestDTO
from backend.models.postgis.statuses import (
ProjectDatabase,
ProjectStatus,
ProjectPriority,
MappingTypes,
Expand All @@ -27,22 +26,6 @@
)
from backend.models.dtos.campaign_dto import CampaignDTO

def is_known_project_database(value):
""" Validates that Project Database is known value """
if value.upper() == "ALL":
return True

if type(value) == list:
return # Don't validate the entire list, just the individual values

try:
ProjectDatabase[value.upper()]
except KeyError:
raise ValidationError(
f"Unknown projectDatabase: {value} Valid values are {ProjectDatabase.OSM.name}, "
f"{ProjectDatabase.PDMAP.name}"
)


def is_known_project_status(value):
"""Validates that Project Status is known value"""
Expand Down Expand Up @@ -160,7 +143,6 @@ class DraftProjectDTO(Model):
database = StringType(
required=True,
serialized_name="database",
validators=[is_known_project_database],
serialize_when_none=False,
)
area_of_interest = BaseType(required=True, serialized_name="areaOfInterest")
Expand Down Expand Up @@ -197,7 +179,6 @@ class ProjectDTO(Model):
database = StringType(
required=True,
serialized_name="database",
validators=[is_known_project_database],
serialize_when_none=False,
)
project_status = StringType(
Expand Down Expand Up @@ -332,7 +313,7 @@ class ProjectSearchDTO(Model):

preferred_locale = StringType(default="en")
difficulty = StringType(validators=[is_known_project_difficulty])
database = StringType(validators=[is_known_project_database])
database = StringType()
action = StringType()
mapping_types = ListType(StringType, validators=[is_known_mapping_type])
mapping_types_exact = BooleanType(required=False)
Expand Down
11 changes: 5 additions & 6 deletions backend/models/postgis/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from backend.models.postgis.project_info import ProjectInfo
from backend.models.postgis.project_chat import ProjectChat
from backend.models.postgis.statuses import (
ProjectDatabase,
ProjectStatus,
ProjectPriority,
TaskStatus,
Expand Down Expand Up @@ -121,7 +120,7 @@ class Project(db.Model):

# Columns
id = db.Column(db.Integer, primary_key=True)
database = db.Column(db.Integer, default=ProjectDatabase.OSM.value, nullable=False)
database = db.Column(db.String, nullable=False)
status = db.Column(db.Integer, default=ProjectStatus.DRAFT.value, nullable=False)
created = db.Column(db.DateTime, default=timestamp, nullable=False)
priority = db.Column(db.Integer, default=ProjectPriority.MEDIUM.value)
Expand Down Expand Up @@ -240,7 +239,7 @@ def create_draft_project(self, draft_project_dto: DraftProjectDTO):
self.organisation = draft_project_dto.organisation

if draft_project_dto.database is not None:
self.database = ProjectDatabase[draft_project_dto.database].value
self.database = draft_project_dto.database

self.status = ProjectStatus.DRAFT.value
self.author_id = draft_project_dto.user_id
Expand Down Expand Up @@ -395,7 +394,7 @@ def get(project_id: int) -> Optional["Project"]:

def update(self, project_dto: ProjectDTO):
"""Updates project from DTO"""
self.database = ProjectDatabase[project_dto.database].value
self.database = project_dto.database
self.status = ProjectStatus[project_dto.project_status].value
self.priority = ProjectPriority[project_dto.project_priority].value
locales = [i.locale for i in project_dto.project_info_locales]
Expand Down Expand Up @@ -871,7 +870,7 @@ def get_project_summary(
summary.random_task_selection_enforced = self.enforce_random_task_selection
summary.private = self.private
summary.license_id = self.license_id
summary.database = ProjectDatabase(self.database).name
summary.database = self.database
summary.status = ProjectStatus(self.status).name
summary.id_presets = self.id_presets
summary.extra_id_params = self.extra_id_params
Expand Down Expand Up @@ -1005,7 +1004,7 @@ def _get_project_and_base_dto(self):
"""Populates a project DTO with properties common to all roles"""
base_dto = ProjectDTO()
base_dto.project_id = self.id
base_dto.database= ProjectDatabase(self.database).name
base_dto.database= self.database
base_dto.project_status = ProjectStatus(self.status).name
base_dto.default_locale = self.default_locale
base_dto.project_priority = ProjectPriority(self.priority).name
Expand Down
5 changes: 2 additions & 3 deletions backend/services/project_search_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
ValidationPermission,
MappingPermission,
ProjectDifficulty,
ProjectDatabase,
)
from backend.models.postgis.campaign import Campaign
from backend.models.postgis.organisation import Organisation
Expand Down Expand Up @@ -128,7 +127,7 @@ def create_result_dto(project, preferred_locale, total_contributors):
list_dto.locale = project_info_dto.locale
list_dto.name = project_info_dto.name
list_dto.priority = ProjectPriority(project.priority).name
list_dto.database = ProjectDatabase(project.database).name
list_dto.database = project.database
list_dto.difficulty = ProjectDifficulty(project.difficulty).name
list_dto.short_description = project_info_dto.short_description
list_dto.last_updated = project.last_updated
Expand Down Expand Up @@ -260,7 +259,7 @@ def _filter_projects(search_dto: ProjectSearchDTO, user):
)
if search_dto.database and search_dto.database.upper() != "ALL":
query = query.filter(
Project.database == ProjectDatabase[search_dto.database].value
Project.database == search_dto.database
)
if search_dto.difficulty and search_dto.difficulty.upper() != "ALL":
query = query.filter(
Expand Down
36 changes: 36 additions & 0 deletions frontend/src/components/formInputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,42 @@ export function OrganisationSelectInput({ className }) {
);
}

export const SandboxBoxSelect = ({ className, boxId, onChange }) => {
const [boxes, setBoxes] = useState([]);

useEffect(() => {
fetch(`https://dashboard.osmsandbox.us/v1/boxes`)
.then(response => response.json())
.then(result => {
/*result.push({
name: "✦ New Box ✦"
});*/
setBoxes(result);
})
.catch(error => {
console.error('Error initializing session:', error);
});
});

const getBoxPlaceholder = (id) => {
const orgs = boxes.filter((org) => org.organisationId === id);
return orgs.length ? orgs[0].name : <FormattedMessage {...messages.selectBox} />;
};

return (
<Select
classNamePrefix="react-select"
isClearable={false}
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.name}
options={boxes}
placeholder={getBoxPlaceholder(boxId)}
onChange={onChange}
className={className}
/>
);
};

export function UserCountrySelect({ className, isDisabled = false }: Object) {
const locale = useSelector((state) => state.preferences.locale);
const [options, setOptions] = useState([]);
Expand Down
7 changes: 2 additions & 5 deletions frontend/src/components/mapDatabase.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import messages from './messages';

export const MapDatabaseMessage = (props) => {
const { db, ...otherProps } = props;
const message = ['ALL', 'PDMAP', 'OSM'].includes(db) ? (
<FormattedMessage {...messages[`database${db}`]} />
) : (
db
);
const id = ['OSM', ''].includes(db) ? 'OSM' : 'PDMAP';
const message = <FormattedMessage {...messages[`database${id}`]} />;
return <span {...otherProps}>{message}</span>;
};
4 changes: 4 additions & 0 deletions frontend/src/components/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export default defineMessages({
id: 'formInputs.organisation.select',
defaultMessage: 'Select organization',
},
selectBox: {
id: 'formInputs.box.select',
defaultMessage: 'Select box…',
},
country: {
id: 'formInputs.country.select',
defaultMessage: 'Country',
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/pdEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { useSelector, useDispatch } from 'react-redux';
import * as iD from '@osm-sandbox/public-domain-id';
import '@osm-sandbox/public-domain-id/dist/iD.css';

import { PD_CLIENT_ID, PD_CLIENT_SECRET, PD_SERVER_URL, BASE_URL } from '../config';
import { PD_CLIENT_ID, PD_CLIENT_SECRET, BASE_URL } from '../config';

export default function PDEditor({ setDisable, comment, presets, imagery, gpxUrl }) {
export default function PDEditor({ setDisable, comment, presets, imagery, apiUrl, gpxUrl }) {

const dispatch = useDispatch();
const session = useSelector((state) => state.auth.session);
Expand Down Expand Up @@ -89,7 +89,7 @@ export default function PDEditor({ setDisable, comment, presets, imagery, gpxUrl
}

iDContext.connection().switch({
url: PD_SERVER_URL,
url: apiUrl,
client_id: PD_CLIENT_ID,
client_secret: PD_CLIENT_SECRET,
//redirect_uri: BASE_URL + '/static/id/land.html',
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/projectCard/projectCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export function ProjectCard({
<article
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
className={`relative blue-dark db-${database}`}
className={`relative blue-dark db-${database === '' ? 'OSM' : 'PDMAP'}`}
>
<Link className="no-underline color-inherit" to={`/projects/${projectId}`}>
<div
Expand Down Expand Up @@ -122,7 +122,7 @@ export function ProjectCard({
<div className="pt2 truncate flex justify-between items-center">
<div className="f6 blue-grey">#{projectId}</div>
<span>
<img src={database === 'OSM' ? ("/osm-logo-mono.svg") : ("/pdmap-logo-mono.svg")} className="h1 v-mid mr1"/>
<img src={database === '' ? ("/osm-logo-mono.svg") : ("/pdmap-logo-mono.svg")} className="h1 v-mid mr1"/>
<MapDatabaseMessage
db={database}
className="blue-grey db-label f6 ttc fw5 truncate"
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/projectCreate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ const ProjectCreate = () => {
throw new Error('Project name validation error.');
}
}
if (metadata.database === 'PDMAP') {
setErr({ error: true, message: intl.formatMessage(messages.noBox) });
throw new Error('Missing database.');
}
if (!metadata.geom) {
setErr({ error: true, message: intl.formatMessage(messages.noGeometry) });
throw new Error('Missing geom.');
Expand All @@ -221,7 +225,7 @@ const ProjectCreate = () => {
areaOfInterest: truncate(metadata.geom, { precision: 6 }),
projectName: metadata.projectName,
organisation: metadata.organisation || cloneProjectData.organisation,
database: metadata.database,
database: metadata.database === 'OSM' ? '' : metadata.database,
tasks: truncate(metadata.taskGrid, { precision: 6 }),
arbitraryTasks: metadata.arbitraryTasks,
};
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/components/projectCreate/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ export default defineMessages({
id: 'management.projects.create.errors.project_name_validation_error',
defaultMessage: 'Project name should start with an alphabet.',
},
noBox: {
id: 'management.projects.create.errors.nobox',
defaultMessage: "You need to select the project's box.",
},
noGeometry: {
id: 'management.projects.create.errors.no_geometry',
defaultMessage: "You need to define the project's area of interest.",
Expand Down
20 changes: 18 additions & 2 deletions frontend/src/components/projectCreate/review.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import messages from './messages';
import { MapDatabaseMessage } from '../mapDatabase';
import { Alert } from '../alert';

import { OrganisationSelect } from '../formInputs';
import {
OrganisationSelect,
SandboxBoxSelect,
} from '../formInputs';

export default function Review({ metadata, updateMetadata, selectedOrgObj, updateSelectedOrgObj, token, projectId, cloneProjectData }) {
const [error, setError] = useState(null);
Expand Down Expand Up @@ -76,7 +79,7 @@ export default function Review({ metadata, updateMetadata, selectedOrgObj, updat
<input
disabled={selectedOrgObj.databases.length < 2}
value={option.value}
checked={metadata.database === option.value}
checked={option.value === (metadata.database === 'OSM' ? 'OSM' : 'PDMAP')}
onChange={() =>
updateMetadata({
...metadata,
Expand All @@ -91,6 +94,19 @@ export default function Review({ metadata, updateMetadata, selectedOrgObj, updat
))}
</>
) : null}

{metadata.database !== "OSM" ? (
<SandboxBoxSelect
boxId={metadata.database}
onChange={(option) => {
setError(null);
var updatedMeta = { ...metadata, database: option.name || '' };
updateMetadata(updatedMeta);
}}
className="z-5 w-75 pt3"
/>
) : null}

{selectedOrgObj.databases && selectedOrgObj.databases.length === 1 ? (
<p className="pt2">
<FormattedMessage {...messages[`limitedOrgDatabases${selectedOrgObj.databases[0]}`]} />
Expand Down
23 changes: 10 additions & 13 deletions frontend/src/components/projectDetail/infoPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,18 @@ import { BigProjectTeaser } from './bigProjectTeaser';
import { useComputeCompleteness } from '../../hooks/UseProjectCompletenessCalc';

const ProjectDatabaseInfo = (props) => {
//if (props.db === 'OSM') {
//<FormattedMessage {...messages.database} />
return (
<div className="cf">
<div className="w-50-ns w-70 fl">
<h3 className='db ttu f6 blue-light mb2'>
<FormattedMessage {...messages.database} />
</h3>
<div className="db fl pt1">
<a target="_blank" href={ props.db === 'PDMAP' ? 'https://publicdomainmap.org/' : 'https://www.openstreetmap.org/about'}><MapDatabaseMessage db={props.db} /></a>
</div>
return (
<div className="cf">
<div className="w-50-ns w-70 fl">
<h3 className='db ttu f6 blue-light mb2'>
<FormattedMessage {...messages.database} />
</h3>
<div className="db fl pt1">
<a target="_blank" href={ props.db === '' ? 'https://www.openstreetmap.org/about' : 'https://publicdomainmap.org/' }><MapDatabaseMessage db={props.db} /></a>
</div>
</div>
);
//}
</div>
);
};

const ProjectDetailTypeBar = (props) => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/projectEdit/descriptionForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const DescriptionForm = ({ languages }) => {
<input
disabled
value={option.value}
checked={projectInfo.database === option.value}
checked={option.value === (projectInfo.database === '' ? 'OSM' : 'PDMAP')}
onChange={() =>
setProjectInfo({
...projectInfo,
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/taskSelection/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export function TaskMapAction({ project, tasks, activeTasks, getTasks, action, e
</div>
}
>
{project.database === 'OSM' ? (
{project.database === '' ? (
editor === 'ID' ? (
<Editor
setDisable={setDisable}
Expand All @@ -246,6 +246,7 @@ export function TaskMapAction({ project, tasks, activeTasks, getTasks, action, e
comment={project.changesetComment}
presets={project.idPresets}
imagery={formatImageryUrlCallback(project.imagery)}
apiUrl={`https://api.${project.database}.boxes.osmsandbox.us`}
gpxUrl={getTaskGpxUrlCallback(project.projectId, tasksIds)}
/>
)
Expand All @@ -272,7 +273,7 @@ export function TaskMapAction({ project, tasks, activeTasks, getTasks, action, e
)}
</div>
{showSidebar ? (
<div className={`w-30 fr pt3 ph3 h-100 overflow-y-scroll base-font bg-white db-${project.database}`}>
<div className={`w-30 fr pt3 ph3 h-100 overflow-y-scroll base-font bg-white db-${project.database === '' ? 'OSM' : 'PDMAP'}`}>
<ReactPlaceholder
showLoadingAnimation={true}
rows={3}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/taskSelection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export function TaskSelection({ project }: Object) {
: randomTask;

return (
<div className={`cf vh-minus-200-ns db-${project.database}`}>
<div className={`cf vh-minus-200-ns db-${project.database === '' ? 'OSM' : 'PDMAP'}`}>
<div className="cf vh-minus-200-ns">
{!isUserTeamsLoading &&
['mappingIsComplete', 'selectAnotherProject'].includes(taskAction) && (
Expand Down
Loading

0 comments on commit cb54788

Please sign in to comment.