forked from ytgov/internal-data-portal
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #55 from icefoganalytics/issue-54/dataset-visualiz…
…e-manage-page Part 1: Dataset Visualize Manage Page
- Loading branch information
Showing
40 changed files
with
1,538 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
api/src/controllers/visualization-controls-controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
73 changes: 73 additions & 0 deletions
73
api/src/db/migrations/2024.02.26T21.39.25.create-visualization-controls-table.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
35 changes: 35 additions & 0 deletions
35
api/src/db/migrations/2024.02.26T21.47.11.backfill-visualization-controls-table.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | ||
) | ||
} |
61 changes: 61 additions & 0 deletions
61
api/src/db/migrations/2024.02.27T20.41.43.create-search-field-exclusions-table.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { matchesGrantLevel } from "./matches-grant-level" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
Oops, something went wrong.