Skip to content

Commit

Permalink
Merge pull request #55 from icefoganalytics/issue-54/dataset-visualiz…
Browse files Browse the repository at this point in the history
…e-manage-page

Part 1: Dataset Visualize Manage Page
  • Loading branch information
klondikemarlen authored Feb 28, 2024
2 parents f360a99 + ebde2ec commit c0a4478
Show file tree
Hide file tree
Showing 40 changed files with 1,538 additions and 70 deletions.
1 change: 1 addition & 0 deletions api/src/controllers/datasets-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export class DatasetsController extends BaseController {
"stewardship",
"accessGrants",
"accessRequests",
"visualizationControl",
],
})

Expand Down
1 change: 1 addition & 0 deletions api/src/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { TaggingsController } from "./taggings-controller"
export { TagsController } from "./tags-controller"
export { UserGroupsController } from "./user-groups-controller"
export { UsersController } from "./users-controller"
export { VisualizationControlsController } from "./visualization-controls-controller"

export * as AccessRequests from "./access-requests"
export * as Users from "./users"
Expand Down
106 changes: 106 additions & 0 deletions api/src/controllers/visualization-controls-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { isNil } from "lodash"

import { VisualizationControl } from "@/models"
import { VisualizationControlsPolicy } from "@/policies"
import { UpdateService } from "@/services/visualization-controls"
import { ShowSerializer } from "@/serializers/visualization-controls"

import BaseController from "@/controllers/base-controller"

export class VisualizationControlsController extends BaseController {
async show() {
const visualizationControl = await this.loadVisualizationControl()
if (isNil(visualizationControl)) {
return this.response.status(404).json({ message: "Visualization control not found." })
}

const policy = this.buildPolicy(visualizationControl)
if (!policy.show()) {
return this.response
.status(403)
.json({ message: "You are not authorized to view this visualization control." })
}

try {
const serializedVisualizationControl = ShowSerializer.perform(
visualizationControl,
this.currentUser
)
return this.response
.status(200)
.json({ visualizationControl: serializedVisualizationControl })
} catch (error) {
return this.response
.status(500)
.json({ message: `Visualization controller serialization failed: ${error}` })
}
}

async update() {
const visualizationControl = await this.loadVisualizationControl()
if (isNil(visualizationControl)) {
return this.response.status(404).json({ message: "Visualization control not found." })
}

const policy = this.buildPolicy(visualizationControl)
if (!policy.update()) {
return this.response
.status(403)
.json({ message: "You are not authorized to update this visualization control." })
}

const permittedAttributes = policy.permitAttributesForUpdate(this.request.body)

let updatedVisualizationControl: VisualizationControl
try {
updatedVisualizationControl = await UpdateService.perform(
visualizationControl,
permittedAttributes,
this.currentUser
)
} catch (error) {
return this.response
.status(422)
.json({ message: `Visualization control update failed: ${error}` })
}

try {
const serializedVisualizationControl = ShowSerializer.perform(
updatedVisualizationControl,
this.currentUser
)
return this.response
.status(200)
.json({ visualizationControl: serializedVisualizationControl })
} catch (error) {
return this.response
.status(500)
.json({ message: `Visualization controller serialization failed: ${error}` })
}
}

private async loadVisualizationControl(): Promise<VisualizationControl | null> {
return VisualizationControl.findByPk(this.params.visualizationControlId, {
include: [
{
association: "dataset",
include: [
{
association: "owner",
include: ["groupMembership"],
},
"accessGrants",
"accessRequests",
],
},
"searchFieldExclusions",
],
})
}

private buildPolicy(record: VisualizationControl): VisualizationControlsPolicy {
return new VisualizationControlsPolicy(this.currentUser, record)
}
}

export default VisualizationControlsController
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { DataTypes } from "sequelize"

import type { Migration } from "@/db/umzug"
import { MssqlSimpleTypes } from "@/db/utils/mssql-simple-types"

export const up: Migration = async ({ context: queryInterface }) => {
await queryInterface.createTable("visualization_controls", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true,
},
dataset_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: "datasets",
key: "id",
},
},
is_dowloadable_as_csv: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
has_search_restrictions: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
has_search_field_restrictions: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
has_search_row_limits: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
search_row_limit_maximum: {
type: DataTypes.INTEGER,
allowNull: true,
},
created_at: {
type: MssqlSimpleTypes.DATETIME2(0),
allowNull: false,
defaultValue: MssqlSimpleTypes.NOW,
},
updated_at: {
type: MssqlSimpleTypes.DATETIME2(0),
allowNull: false,
defaultValue: MssqlSimpleTypes.NOW,
},
deleted_at: {
type: MssqlSimpleTypes.DATETIME2(0),
allowNull: true,
},
})

await queryInterface.addIndex("visualization_controls", ["dataset_id"], {
unique: true,
name: "unique_visualization_controls_on_dataset_id",
where: {
deleted_at: null,
},
})
}

export const down: Migration = async ({ context: queryInterface }) => {
await queryInterface.dropTable("visualization_controls")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { QueryTypes } from "sequelize"

import type { Migration } from "@/db/umzug"

export const up: Migration = async ({ context: queryInterface }) => {
await queryInterface.sequelize.query(
/* sql */ `
INSERT INTO
visualization_controls(dataset_id)
SELECT
id
FROM
datasets;
`,
{ type: QueryTypes.INSERT }
)
}

export const down: Migration = async ({ context: queryInterface }) => {
await queryInterface.sequelize.query(
/* sql */ `
DELETE FROM
visualization_controls
WHERE 1=1;
`,
{ type: QueryTypes.DELETE }
)
// Reset the auto-increment counter
await queryInterface.sequelize.query(
/* sql */ `
DBCC CHECKIDENT ('visualization_controls', RESEED, 0);
`,
{ type: QueryTypes.UPDATE }
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { DataTypes } from "sequelize"

import type { Migration } from "@/db/umzug"
import { MssqlSimpleTypes } from "@/db/utils/mssql-simple-types"

export const up: Migration = async ({ context: queryInterface }) => {
await queryInterface.createTable("search_field_exclusions", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true,
},
visualization_control_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: "visualization_controls",
key: "id",
},
},
dataset_field_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: "dataset_fields",
key: "id",
},
},
created_at: {
type: MssqlSimpleTypes.DATETIME2(0),
allowNull: false,
defaultValue: MssqlSimpleTypes.NOW,
},
updated_at: {
type: MssqlSimpleTypes.DATETIME2(0),
allowNull: false,
defaultValue: MssqlSimpleTypes.NOW,
},
deleted_at: {
type: MssqlSimpleTypes.DATETIME2(0),
allowNull: true,
},
})

await queryInterface.addIndex(
"search_field_exclusions",
["visualization_control_id", "dataset_field_id"],
{
unique: true,
name: "unique_search_field_exclusions_on_visualization_control_id_and_dataset_field_id",
where: {
deleted_at: null,
},
}
)
}

export const down: Migration = async ({ context: queryInterface }) => {
await queryInterface.dropTable("search_field_exclusions")
}
1 change: 1 addition & 0 deletions api/src/models/access-grants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { matchesGrantLevel } from "./matches-grant-level"
52 changes: 52 additions & 0 deletions api/src/models/access-grants/matches-grant-level.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { isNil } from "lodash"

import { GrantLevels } from "@/models/access-grant"
import UserGroupMembership from "@/models/user-group-membership"

export function matchesGrantLevel(
grantLevel: GrantLevels,
ownerGroupMembership: UserGroupMembership,
requestingUserGroupMembership: UserGroupMembership
): boolean {
if (grantLevel === GrantLevels.GOVERNMENT_WIDE) {
return true
} else if (
grantLevel === GrantLevels.DEPARTMENT &&
!isNil(ownerGroupMembership.departmentId) &&
ownerGroupMembership.departmentId === requestingUserGroupMembership.departmentId
) {
return true
} else if (
grantLevel === GrantLevels.DIVISION &&
!isNil(ownerGroupMembership.departmentId) &&
!isNil(ownerGroupMembership.divisionId) &&
ownerGroupMembership.departmentId === requestingUserGroupMembership.departmentId &&
ownerGroupMembership.divisionId === requestingUserGroupMembership.divisionId
) {
return true
} else if (
grantLevel === GrantLevels.BRANCH &&
!isNil(ownerGroupMembership.departmentId) &&
!isNil(ownerGroupMembership.divisionId) &&
!isNil(ownerGroupMembership.branchId) &&
ownerGroupMembership.departmentId === requestingUserGroupMembership.departmentId &&
ownerGroupMembership.divisionId === requestingUserGroupMembership.divisionId &&
ownerGroupMembership.branchId === requestingUserGroupMembership.branchId
) {
return true
} else if (
grantLevel === GrantLevels.UNIT &&
!isNil(ownerGroupMembership.divisionId) &&
!isNil(ownerGroupMembership.departmentId) &&
!isNil(ownerGroupMembership.branchId) &&
!isNil(ownerGroupMembership.unitId) &&
ownerGroupMembership.departmentId === requestingUserGroupMembership.departmentId &&
ownerGroupMembership.divisionId === requestingUserGroupMembership.divisionId &&
ownerGroupMembership.branchId === requestingUserGroupMembership.branchId &&
ownerGroupMembership.unitId === requestingUserGroupMembership.unitId
) {
return true
} else {
return false
}
}
Loading

0 comments on commit c0a4478

Please sign in to comment.