diff --git a/addons/api/addon/generated/models/policy.js b/addons/api/addon/generated/models/policy.js new file mode 100644 index 000000000..91f8b8c1a --- /dev/null +++ b/addons/api/addon/generated/models/policy.js @@ -0,0 +1,61 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import BaseModel from '../../models/base'; +import { attr } from '@ember-data/model'; + +/** + * Storage Policy is a resource that manages the lifecycle of Session Recordings, + * particularly to meet compliance frameworks. + */ +export default class GeneratedPolicyModel extends BaseModel { + // =attributes + + @attr('string', { + description: 'The type of the resource, to help differentiate schemas', + }) + type; + + @attr('string', { + description: 'Optional name for identification purposes.', + }) + name; + + @attr('string', { + description: 'Optional user-set description for identification purposes.', + }) + description; + + @attr('date', { + description: 'The time this resource was created\nOutput only.', + readOnly: true, + }) + created_time; + + @attr('date', { + description: 'The time this resource was last updated\nOutput only.', + readOnly: true, + }) + updated_time; + + @attr('number', { + description: 'Current version number of this resource.', + }) + version; + + @attr('object', { + isNestedAttribute: true, + description: + 'Days is the number of days for which a session recording will be retained.', + }) + retain_for; + + @attr('object', { + isNestedAttribute: true, + description: + 'Days is the number of days for which a session recording will be automatically deleted.', + }) + delete_after; +} diff --git a/addons/api/addon/models/policy.js b/addons/api/addon/models/policy.js new file mode 100644 index 000000000..337357212 --- /dev/null +++ b/addons/api/addon/models/policy.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import GeneratedPolicyModel from '../generated/models/policy'; + +export default class PolicyModel extends GeneratedPolicyModel {} diff --git a/addons/api/addon/transforms/object.js b/addons/api/addon/transforms/object.js new file mode 100644 index 000000000..21ebfd352 --- /dev/null +++ b/addons/api/addon/transforms/object.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Transform from '@ember-data/serializer/transform'; +import { TrackedObject } from 'tracked-built-ins'; + +export default class ObjectTransform extends Transform { + /** + * @param serialized + * @returns {object} + */ + deserialize(serialized) { + if (Object.keys(serialized).length) { + const obj = new TrackedObject(serialized); + return obj; + } else { + return {}; + } + } + + /** + * @param deserialized + * @returns {object} + */ + serialize(deserialized) { + if (Object.keys(deserialized).length) { + return deserialized; + } else { + return {}; + } + } +} diff --git a/addons/api/app/models/policy.js b/addons/api/app/models/policy.js new file mode 100644 index 000000000..ff127125c --- /dev/null +++ b/addons/api/app/models/policy.js @@ -0,0 +1,6 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +export { default } from 'api/models/policy'; diff --git a/addons/api/app/transforms/object.js b/addons/api/app/transforms/object.js new file mode 100644 index 000000000..9e432cd72 --- /dev/null +++ b/addons/api/app/transforms/object.js @@ -0,0 +1,6 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +export { default } from 'api/transforms/object'; diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js index 827a1726b..41efe450c 100644 --- a/addons/api/mirage/config.js +++ b/addons/api/mirage/config.js @@ -711,6 +711,22 @@ function routes() { }, ); + //storage-policy + this.get( + '/policies', + ({ policies }, { queryParams: { scope_id: scopeId } }) => { + return policies.where({ scopeId }); + }, + ); + this.get('/policies/:id'); + this.del('/policies/:id'); + this.patch('/policies/:id'); + this.post('/policies', function ({ policies }) { + const attrs = this.normalizedRequestAttrs(); + + return policies.create(attrs); + }); + // session recordings this.get( '/session-recordings', diff --git a/addons/api/mirage/factories/policy.js b/addons/api/mirage/factories/policy.js new file mode 100644 index 000000000..f17d94e0b --- /dev/null +++ b/addons/api/mirage/factories/policy.js @@ -0,0 +1,35 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import factory from '../generated/factories/policy'; +import permissions from '../helpers/permissions'; +import { faker } from '@faker-js/faker'; +import generateId from '../helpers/id'; + +export default factory.extend({ + id: () => generateId('pst_'), + + authorized_actions: () => + permissions.authorizedActionsFor('policy') || [ + 'no-op', + 'read', + 'update', + 'delete', + ], + + attributes() { + return { + retain_for: { + days: faker.number.int({ min: 10, max: 700 }), + overridable: faker.datatype.boolean(), + }, + + delete_after: { + days: faker.number.int({ min: 1, max: 500 }), + overridable: faker.datatype.boolean(), + }, + }; + }, +}); diff --git a/addons/api/mirage/factories/scope.js b/addons/api/mirage/factories/scope.js index 210c854c4..b1fb50979 100644 --- a/addons/api/mirage/factories/scope.js +++ b/addons/api/mirage/factories/scope.js @@ -37,6 +37,7 @@ export default factory.extend({ collectionActions['session-recordings'] = ['list']; collectionActions.users = ['create', 'list']; collectionActions['auth-methods'] = ['create', 'list']; + collectionActions['policies'] = ['create', 'list']; } if (this.type === 'project') { diff --git a/addons/api/mirage/generated/factories/policy.js b/addons/api/mirage/generated/factories/policy.js new file mode 100644 index 000000000..b5a8fee5e --- /dev/null +++ b/addons/api/mirage/generated/factories/policy.js @@ -0,0 +1,19 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { Factory } from 'miragejs'; +import { faker } from '@faker-js/faker'; + +/** + * GeneratedStoragePolicyModelFactory + */ +export default Factory.extend({ + name: () => faker.word.words(), + type: 'storage', + description: () => faker.word.words(), + created_time: () => faker.date.recent(), + updated_time: () => faker.date.recent(), + version: () => faker.number.int(), +}); diff --git a/addons/api/mirage/models/policy.js b/addons/api/mirage/models/policy.js new file mode 100644 index 000000000..a68d129c9 --- /dev/null +++ b/addons/api/mirage/models/policy.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { belongsTo } from 'miragejs'; +import Model from './base'; +export default Model.extend({ + scope: belongsTo(), +}); diff --git a/addons/api/mirage/scenarios/default.js b/addons/api/mirage/scenarios/default.js index 34c0c5b24..351f1dec0 100644 --- a/addons/api/mirage/scenarios/default.js +++ b/addons/api/mirage/scenarios/default.js @@ -104,6 +104,10 @@ export default function (server) { server.createList('storage-bucket', 3, { scope: globalScope }); server.createList('storage-bucket', 2, { scope: orgScope }); + // Storage Policies + server.createList('policy', 3, { scope: globalScope }); + server.createList('policy', 3, { scope: orgScope }); + // Other resources server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => { server.createList('host-catalog', 8, { scope }, 'withChildren'); diff --git a/addons/api/package.json b/addons/api/package.json index 0a8834a91..147e39ede 100644 --- a/addons/api/package.json +++ b/addons/api/package.json @@ -28,7 +28,9 @@ "ember-cli-htmlbars": "^6.2.0", "ember-cli-mirage": "^2.4.0", "ember-data": "~4.12.0", - "ember-fetch": "^8.1.1" + "ember-fetch": "^8.1.1", + "ember-auto-import": "^2.6.3", + "tracked-built-ins": "^3.1.1" }, "devDependencies": { "@babel/core": "^7.21.0", @@ -43,7 +45,6 @@ "broccoli-asset-rev": "^3.0.0", "concurrently": "^8.0.1", "doctoc": "^2.2.0", - "ember-auto-import": "^2.6.3", "ember-cli": "^4.12.1", "ember-cli-dependency-checker": "^3.3.1", "ember-cli-inject-live-reload": "^2.1.0", diff --git a/addons/api/tests/unit/models/policy-test.js b/addons/api/tests/unit/models/policy-test.js new file mode 100644 index 000000000..dbf5a3171 --- /dev/null +++ b/addons/api/tests/unit/models/policy-test.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | storage policy', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let store = this.owner.lookup('service:store'); + let model = store.createRecord('policy', {}); + assert.ok(model); + }); +}); diff --git a/addons/api/tests/unit/transforms/object-test.js b/addons/api/tests/unit/transforms/object-test.js new file mode 100644 index 000000000..b8191e1da --- /dev/null +++ b/addons/api/tests/unit/transforms/object-test.js @@ -0,0 +1,41 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Transform | object', function (hooks) { + setupTest(hooks); + + test('it deserializes a policy object', function (assert) { + const transform = this.owner.lookup('transform:object'); + const obj = { + days: 365, + overridable: true, + }; + const expectedObj = { + days: 365, + overridable: true, + }; + const emptyObj = {}; + assert.deepEqual(transform.deserialize(obj), expectedObj); + assert.deepEqual(transform.deserialize(emptyObj), {}); + }); + + test('it serializes a policy object', function (assert) { + const transform = this.owner.lookup('transform:object'); + const obj = { + days: 2, + overridable: true, + }; + const expectedObj = { + days: 2, + overridable: true, + }; + const emptyObj = {}; + assert.deepEqual(transform.serialize(obj), expectedObj); + assert.deepEqual(transform.serialize(emptyObj), {}); + }); +});