Skip to content

Commit

Permalink
Merge branch 'develop' into feature/api/MRXN23-444-range-of-amount-fo…
Browse files Browse the repository at this point in the history
…r-feature-per-pu
  • Loading branch information
hotzevzl authored Oct 20, 2023
2 parents 4268a88 + aa30d0b commit 29122bd
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 12 deletions.
1 change: 1 addition & 0 deletions .github/workflows/tests-api-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
timeout-minutes: 60
strategy:
fail-fast: false
max-parallel: 6
matrix:
test-suite:
- 'access-control'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/tests-geoprocessing-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 6
matrix:
test-suite:
- 'cost-template'
Expand Down
37 changes: 33 additions & 4 deletions api/apps/api/src/modules/geo-features/geo-features.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,17 @@ import { GeoFeaturesRequestInfo } from './geo-features-request-info';
import { antimeridianBbox, nominatim2bbox } from '@marxan/utils/geo';
import { Either, left, right } from 'fp-ts/lib/Either';
import { ProjectAclService } from '@marxan-api/modules/access-control/projects-acl/project-acl.service';
import { projectNotFound } from '@marxan-api/modules/projects/projects.service';
import {
projectNotFound,
projectNotVisible,
} from '@marxan-api/modules/projects/projects.service';
import { UpdateFeatureNameDto } from '@marxan-api/modules/geo-features/dto/update-feature-name.dto';
import { ScenarioFeaturesService } from '@marxan-api/modules/scenarios-features';
import { GeoFeatureTag } from '@marxan-api/modules/geo-feature-tags/geo-feature-tag.api.entity';
import { GeoFeatureTagsService } from '@marxan-api/modules/geo-feature-tags/geo-feature-tags.service';
import {
featureNotFoundWithinProject,
GeoFeatureTagsService,
} from '@marxan-api/modules/geo-feature-tags/geo-feature-tags.service';
import { FeatureAmountUploadService } from '@marxan-api/modules/geo-features/import/features-amounts-upload.service';
import { isNil } from 'lodash';

Expand Down Expand Up @@ -835,8 +841,6 @@ export class GeoFeaturesService extends AppBaseService<
} as GeoFeature;
}

// @TODO: update tests once saving amounts in puvspr_calculations is consolidates

async saveAmountRangeForFeatures(featureIds: string[]) {
this.logger.log(`Saving min and max amounts for new features...`);

Expand Down Expand Up @@ -868,4 +872,29 @@ export class GeoFeaturesService extends AppBaseService<
where features.id = minmax.feature_id;`;
await this.geoFeaturesRepository.query(query);
}

async checkProjectFeatureVisibility(
userId: string,
projectId: string,
featureId: string,
): Promise<
Either<
typeof featureNotFoundWithinProject | typeof projectNotVisible,
GeoFeature
>
> {
const projectFeature = await this.geoFeaturesRepository.findOne({
where: { id: featureId, projectId },
});

console.log('projectFeature', projectFeature);
if (!projectFeature) {
return left(featureNotFoundWithinProject);
}
if (!(await this.projectAclService.canViewProject(userId, projectId))) {
return left(projectNotVisible);
}

return right(projectFeature);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Post,
Query,
Req,
Res,
UploadedFile,
UseGuards,
UseInterceptors,
Expand All @@ -26,6 +27,7 @@ import {
ApiOkResponse,
ApiOperation,
ApiParam,
ApiQuery,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';
Expand Down Expand Up @@ -76,6 +78,8 @@ import { GeoFeatureTagsService } from '@marxan-api/modules/geo-feature-tags/geo-
import { GetProjectTagsResponseDto } from '@marxan-api/modules/projects/dto/get-project-tags-response.dto';
import { UpdateProjectTagDTO } from '@marxan-api/modules/projects/dto/update-project-tag.dto';
import { isNil } from 'lodash';
import { Response } from 'express';
import { ProxyService } from '@marxan-api/modules/proxy/proxy.service';

@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
Expand All @@ -88,6 +92,7 @@ export class ProjectFeaturesController {
private readonly geoFeatureService: GeoFeaturesService,
private readonly geoFeatureTagsService: GeoFeatureTagsService,
private readonly shapefileService: ShapefileService,
private readonly proxyService: ProxyService,
) {}

@IsMissingAclImplementation()
Expand Down Expand Up @@ -427,4 +432,71 @@ export class ProjectFeaturesController {
});
}
}

@ImplementsAcl()
@ApiOperation({
description: 'Get tile for a project feature by project id and feature id.',
})
@ApiParam({
name: 'z',
description: 'The zoom level ranging from 0 - 20',
type: Number,
required: true,
})
@ApiParam({
name: 'x',
description: 'The tile x offset on Mercator Projection',
type: Number,
required: true,
})
@ApiParam({
name: 'y',
description: 'The tile y offset on Mercator Projection',
type: Number,
required: true,
})
@ApiParam({
name: 'projectId',
description: 'Id of the project',
type: String,
required: true,
})
@ApiParam({
name: 'featureId',
description: 'Id of the feature',
type: String,
required: true,
})
@ApiQuery({
name: 'bbox',
description: 'Bounding box of the project [xMin, xMax, yMin, yMax]',
type: [Number],
required: false,
example: [-1, 40, 1, 42],
})
@Get(':projectId/features/:featureId/preview/tiles/:z/:x/:y.mvt')
async proxyFeatureTile(
@Req() req: RequestWithAuthenticatedUser,
@Res() response: Response,
@Param('projectId', ParseUUIDPipe) projectId: string,
@Param('featureId', ParseUUIDPipe) featureId: string,
): Promise<void> {
const checkCostSurfaceForProject =
await this.geoFeatureService.checkProjectFeatureVisibility(
req.user.id,
projectId,
featureId,
);
console.log('checkCostSurfaceForProject', checkCostSurfaceForProject);
if (isLeft(checkCostSurfaceForProject)) {
throw mapAclDomainToHttpError(checkCostSurfaceForProject.left);
}

req.url = req.url.replace(
`projects/${projectId}/features`,
`geo-features/project-feature`,
);

return await this.proxyService.proxyTileRequest(req, response);
}
}
17 changes: 17 additions & 0 deletions api/apps/geoprocessing/src/modules/features/features.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ export class FeaturesController {
): Promise<Response> {
const tile: Buffer = await this.service.findTile(
TileSpecification,
false,
query.bbox as BBox,
);
setTileResponseHeadersForSuccessfulRequests(response);
return response.send(tile);
}

@Get('project-feature/:id/preview/tiles/:z/:x/:y.mvt')
@ApiBadRequestResponse()
async getTileForProjectFeature(
@Param() TileSpecification: TileSpecification,
@Query() query: FeaturesFilters,
@Res() response: Response,
): Promise<Response> {
const tile: Buffer = await this.service.findTile(
TileSpecification,
true,
query.bbox as BBox,
);
setTileResponseHeadersForSuccessfulRequests(response);
Expand Down
18 changes: 16 additions & 2 deletions api/apps/geoprocessing/src/modules/features/features.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,26 @@ export class FeatureService {
*/
public findTile(
tileSpecification: TileSpecification,
forProject: boolean,
bbox?: BBox,
): Promise<Buffer> {
const { z, x, y, id } = tileSpecification;
const simplificationLevel = 360 / (Math.pow(2, z + 1) * 100);
const attributes = 'feature_id, properties';
const table = `(select ST_RemoveRepeatedPoints((st_dump(the_geom)).geom, ${simplificationLevel}) as the_geom, (coalesce(properties,'{}'::jsonb) || jsonb_build_object('amount', amount)) as properties, feature_id from "${this.featuresRepository.metadata.tableName}")`;
const attributes = forProject
? 'feature_id, amount'
: 'feature_id, properties';
const table = forProject
? `(SELECT ST_RemoveRepeatedPoints((st_dump(the_geom)).geom, ${simplificationLevel}) AS the_geom,
amount,
feature_id
FROM puvspr_calculations
INNER JOIN projects_pu ppu on ppu.id=puvspr_calculations.project_pu_id
INNER JOIN planning_units_geom pug on pug.id=ppu.geom_id)`
: `(select ST_RemoveRepeatedPoints((st_dump(the_geom)).geom, ${simplificationLevel}) as the_geom,
(coalesce(properties,'{}'::jsonb) || jsonb_build_object('amount', amount)) as properties,
feature_id
from "${this.featuresRepository.metadata.tableName}")`;

const customQuery = this.buildFeaturesWhereQuery(id, bbox);
return this.tileService.getTile({
z,
Expand Down
2 changes: 1 addition & 1 deletion app/components/modal/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const CONTENT_CLASSES = {
wide: `w-full sm:w-10/12 md:w-10/12 lg:w-10/12 xl:w-9/12 2xl:w-6/12 ${COMMON_CONTENT_CLASSES}`,
};

const OVERLAY_CLASSES = 'z-50 fixed inset-0 bg-black bg-blur';
const OVERLAY_CLASSES = 'z-40 fixed inset-0 bg-black bg-blur';

export const Modal: React.FC<ModalProps> = ({
id,
Expand Down
15 changes: 13 additions & 2 deletions app/layout/projects/new/form/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,26 @@ const ProjectForm = ({ onFormUpdate }: ProjectFormProps): JSX.Element => {
organizationId: organizationsData[0].id || '7f1fb7f8-1246-4509-89b9-f48b6f976e3f',
} satisfies NewProjectFields & { organizationId: string };

addToast(
'info-project-creation',
<>
<h2 className="font-medium">Your project is being created.</h2>
<p className="text-sm">This might take a few seconds.</p>
</>,
{
level: 'info',
}
);

saveProjectMutation.mutate(
{ data },
{
onSuccess: ({ data: { data: p } }) => {
addToast(
'success-project-creation',
<>
<h2 className="font-medium">Success!</h2>
<p className="text-sm">Project saved successfully</p>
<h2 className="font-medium">Your project has been created.</h2>
<p className="text-sm">You will be redirected to the dashboard.</p>
</>,
{
level: 'success',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ export const PlanningAreaGridUploader: React.FC<PlanningAreaGridUploaderProps> =
size="xl"
type="submit"
onClick={() => setOpened(false)}
disabled={loading}
>
Save
</Button>
Expand Down
6 changes: 3 additions & 3 deletions e2e-product-testing/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2553,9 +2553,9 @@ get-assigned-identifiers@^1.2.0:
integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==

get-func-name@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
version "2.0.2"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41"
integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==

get-intrinsic@^1.0.2:
version "1.1.1"
Expand Down

1 comment on commit 29122bd

@vercel
Copy link

@vercel vercel bot commented on 29122bd Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

marxan – ./

marxan-vizzuality1.vercel.app
marxan23.vercel.app
marxan-git-develop-vizzuality1.vercel.app

Please sign in to comment.