Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: site candidate data model #126

Merged
merged 9 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/spacecat-shared-data-access/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ npm install @adobe/spacecat-shared-data-access
- **updatedAt** (String): Timestamp of the last update.
- **GSI1PK** (String): Partition key for the Global Secondary Index.

### SiteCandidates
- **baseURL** (String): Base URL of the site candidate.
- **status** (String): Status of the site candidate (PENDING, IGNORED, APPROVED, ERROR)
- **createdAt** (String): Timestamp of creation.
- **updatedAt** (String): Timestamp of the last update.
- **updatedBy** (String): Slack id of the last person updated the site candidate.

### Audits
- **siteId** (String): Identifier of the site being audited.
- **SK** (String): Sort key, typically a composite of audit type and timestamp.
Expand Down Expand Up @@ -65,6 +72,11 @@ The module provides two main DAOs:
- `updateSite`
- `removeSite`

### Site Candidate Functions
- `upsertSiteCandidate`
- `siteCandidateExists`
- `updateSiteCandidate`

### Audit Functions
- `getAuditsForSite`
- `getAuditForSite`
Expand Down
97 changes: 96 additions & 1 deletion packages/spacecat-shared-data-access/docs/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"ModelMetadata": {
"Author": "Dominique Jäggi",
"DateCreated": "Nov 23, 2023, 07:00 AM",
"DateLastModified": "Nov 23, 2023, 07:00 AM",
"DateLastModified": "Feb 02, 2024, 11:13 AM",
"Description": "",
"AWSService": "Amazon DynamoDB",
"Version": "3.0"
Expand Down Expand Up @@ -430,6 +430,101 @@
}
}
}
},
{
"TableName": "spacecat-services-site-candidates",
"KeyAttributes": {
"PartitionKey": {
"AttributeName": "baseURL",
"AttributeType": "S"
}
},
"NonKeyAttributes": [
ekremney marked this conversation as resolved.
Show resolved Hide resolved
{
"AttributeName": "status",
"AttributeType": "S"
},
{
"AttributeName": "createdAt",
"AttributeType": "S"
},
{
"AttributeName": "updatedAt",
"AttributeType": "S"
},
{
"AttributeName": "updatedBy",
"AttributeType": "S"
},
{
"AttributeName": "siteId",
"AttributeType": "S"
},
{
"AttributeName": "source",
"AttributeType": "S"
}
],
"DataAccess": {
"MySql": {}
},
"SampleDataFormats": {
"lastModifiedAt": [
"date",
"ISO 8601 date and time"
],
"discoveredAt": [
"date",
"ISO 8601 date and time"
],
"createdAt": [
"date",
"ISO 8601 date and time"
],
"updatedBy": [
"identifiers",
"Full name"
],
"updatedAt": [
"date",
"ISO 8601 date and time"
],
"baseUrl": [
"identifiers",
"URL"
],
"siteId": [
"identifiers",
"UUID"
]
},
"BillingMode": "PROVISIONED",
"ProvisionedCapacitySettings": {
"ProvisionedThroughput": {
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5
},
"AutoScalingRead": {
"ScalableTargetRequest": {
"MinCapacity": 1,
"MaxCapacity": 10,
"ServiceRole": "AWSServiceRoleForApplicationAutoScaling_DynamoDBTable"
},
"ScalingPolicyConfiguration": {
"TargetValue": 70
}
},
"AutoScalingWrite": {
"ScalableTargetRequest": {
"MinCapacity": 1,
"MaxCapacity": 10,
"ServiceRole": "AWSServiceRoleForApplicationAutoScaling_DynamoDBTable"
},
"ScalingPolicyConfiguration": {
"TargetValue": 70
}
}
}
}
]
}
3 changes: 2 additions & 1 deletion packages/spacecat-shared-data-access/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"chai": "4.4.1",
"chai-as-promised": "7.1.1",
"dynamo-db-local": "7.3.0",
"sinon": "17.0.1"
"sinon": "17.0.1",
"sinon-chai": "3.7.0"
}
}
31 changes: 31 additions & 0 deletions packages/spacecat-shared-data-access/src/dto/site-candidate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/**
* Data transfer object for Site Candidate.
*/
export const SiteCandidateDto = {
/**
* Converts a Site Candidate object into a DynamoDB item.
* @param {Readonly<SiteCandidate>} siteCandidate - Site Candidate object.
* @returns {{baseURL, siteId, source, status, createdAt, updatedAt, updatedBy}}
*/
toDynamoItem: (siteCandidate) => ({
baseURL: siteCandidate.getBaseURL(),
siteId: siteCandidate.getSiteId(),
source: siteCandidate.getSource(),
status: siteCandidate.getStatus(),
createdAt: siteCandidate.getCreatedAt(),
updatedAt: siteCandidate.getUpdatedAt(),
updatedBy: siteCandidate.getUpdatedBy(),
}),
};
54 changes: 54 additions & 0 deletions packages/spacecat-shared-data-access/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,54 @@ export interface Site {
updateOrganizationId: (organizationId: string) => Site;
}

/**
ekremney marked this conversation as resolved.
Show resolved Hide resolved
* Represents a site candidate.
*/
export interface SiteCandidate {
/**
* Retrieves the base URL of the site candidate.
* @returns {string} The base URL.
*/
getBaseURL: () => string;

/**
* Retrieves the site id of the site candidate.
* Only set after APPROVED state
* @returns {string} site id
*/
getSiteId: () => string;

/**
* Retrieves the source of the site candidate.
* @returns {string} The source
*/
getSource: () => string;

/**
* Retrieves the status of the site candidate.
* @returns {string} The status
*/
getStatus: () => string;

/**
* Retrieves the creation timestamp of the site candidate.
* @returns {string} The creation timestamp.
*/
getCreatedAt: () => string;

/**
* Retrieves the last update timestamp of the site candidate.
* @returns {string} The last update timestamp.
*/
getUpdatedAt: () => string;

/**
* Retrieves the slack id of the person who last updated the site candidate.
* @returns {string} The last update timestamp.
*/
getUpdatedBy: () => string;
}

export interface Organization {
/**
* Retrieves the ID of the site.
Expand Down Expand Up @@ -353,13 +401,19 @@ export interface DataAccess {
removeOrganization: (
organizationId: string,
) => Promise<void>;

// site candidate functions
upsertSiteCandidate: (siteCandidateDate: object) => Promise<SiteCandidate>;
siteCandidateExists: (baseURL: string) => Promise<boolean>;
updateSiteCandidate: (siteCandidate: SiteCandidate) => Promise<SiteCandidate>;
}

interface DataAccessConfig {
tableNameAudits: string;
tableNameLatestAudits: string;
tableNameOrganizations: string,
tableNameSites: string;
tableNameSiteCandidates: string;
indexNameAllSites: string;
indexNameAllSitesOrganizations: string,
indexNameAllSitesByDeliveryType: string;
Expand Down
3 changes: 3 additions & 0 deletions packages/spacecat-shared-data-access/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { createDataAccess } from './service/index.js';
const TABLE_NAME_AUDITS = 'spacecat-services-audits-dev';
const TABLE_NAME_LATEST_AUDITS = 'spacecat-services-latest-audits-dev';
const TABLE_NAME_SITES = 'spacecat-services-sites-dev';
const TABLE_NAME_SITE_CANDIDATES = 'spacecat-services-site-candidates-dev';
const TABLE_NAME_ORGANIZATIONS = 'spacecat-services-organizations-dev';

const INDEX_NAME_ALL_SITES = 'spacecat-services-all-sites-dev';
Expand All @@ -36,6 +37,7 @@ export default function dataAccessWrapper(fn) {
DYNAMO_TABLE_NAME_AUDITS = TABLE_NAME_AUDITS,
DYNAMO_TABLE_NAME_LATEST_AUDITS = TABLE_NAME_LATEST_AUDITS,
DYNAMO_TABLE_NAME_SITES = TABLE_NAME_SITES,
DYNAMO_TABLE_NAME_SITE_CANDIDATES = TABLE_NAME_SITE_CANDIDATES,
DYNAMO_TABLE_NAME_ORGANIZATIONS = TABLE_NAME_ORGANIZATIONS,
DYNAMO_INDEX_NAME_ALL_SITES = INDEX_NAME_ALL_SITES,
DYNAMO_INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE = INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE,
Expand All @@ -49,6 +51,7 @@ export default function dataAccessWrapper(fn) {
tableNameLatestAudits: DYNAMO_TABLE_NAME_LATEST_AUDITS,
tableNameOrganizations: DYNAMO_TABLE_NAME_ORGANIZATIONS,
tableNameSites: DYNAMO_TABLE_NAME_SITES,
tableNameSiteCandidates: DYNAMO_TABLE_NAME_SITE_CANDIDATES,
indexNameAllSites: DYNAMO_INDEX_NAME_ALL_SITES,
indexNameAllOrganizations: DYNAMO_INDEX_NAME_ALL_ORGANIZATIONS,
indexNameAllSitesByDeliveryType: DYNAMO_INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE,
Expand Down
96 changes: 96 additions & 0 deletions packages/spacecat-shared-data-access/src/models/site-candidate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { hasText, isValidUrl } from '@adobe/spacecat-shared-utils';

import { Base } from './base.js';

export const DEFAULT_UPDATED_BY = 'spacecat';

export const SITE_CANDIDATE_SOURCES = {
SPACECAT_SLACK_BOT: 'SPACECAT_SLACK_BOT',
RUM: 'RUM',
CDN: 'CDN',
};

export const SITE_CANDIDATE_STATUS = {
PENDING: 'PENDING', // site candidate notification sent and waiting for human input
ekremney marked this conversation as resolved.
Show resolved Hide resolved
IGNORED: 'IGNORED', // site candidate discarded: not to be added to star catalogue
APPROVED: 'APPROVED', // site candidate is added to star catalogue
ERROR: 'ERROR', // site candidate is discovered
};

/**
* Creates a new Site Candidate.
*
* @param {object} data - site candidate data
* @returns {Readonly<SiteCandidate>} new site candidate
*/
const SiteCandidate = (data = {}) => {
const self = Base({
updatedBy: DEFAULT_UPDATED_BY,
...data,
});
delete self.id; // no id property used in SiteCandidate modal

self.getBaseURL = () => self.state.baseURL;
self.getSiteId = () => self.state.siteId;
self.getSource = () => self.state.source;
self.getStatus = () => self.state.status;
self.getUpdatedBy = () => self.state.updatedBy;

self.setSiteId = (siteId) => {
self.state.siteId = siteId;
self.touch();
return self;
};

self.setSource = (source) => {
self.state.source = source;
self.touch();
return self;
};

self.setStatus = (status) => {
self.state.status = status;
self.touch();
return self;
};

self.setUpdatedBy = (updatedBy) => {
self.state.updatedBy = updatedBy;
self.touch();
return self;
};

return Object.freeze(self);
};

/**
* Creates a new Site Candidate.
*
* @param {object} data - site candidate data
* @returns {Readonly<SiteCandidate>} new site candidate
*/
export const createSiteCandidate = (data) => {
const newState = { ...data };

if (!isValidUrl(newState.baseURL)) {
throw new Error('Base URL must be a valid URL');
}

if (!hasText(newState.updatedBy)) {
newState.updatedBy = DEFAULT_UPDATED_BY;
}

return SiteCandidate(newState);
};
3 changes: 3 additions & 0 deletions packages/spacecat-shared-data-access/src/service/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { createClient } from '@adobe/spacecat-shared-dynamo';
import { auditFunctions } from './audits/index.js';
import { siteFunctions } from './sites/index.js';
import { siteCandidateFunctions } from './site-candidates/index.js';
import { organizationFunctions } from './organizations/index.js';

/**
Expand All @@ -30,11 +31,13 @@ export const createDataAccess = (config, log = console) => {

const auditFuncs = auditFunctions(dynamoClient, config, log);
const siteFuncs = siteFunctions(dynamoClient, config, log);
const siteCandidateFuncs = siteCandidateFunctions(dynamoClient, config, log);
const organizationFuncs = organizationFunctions(dynamoClient, config, log);

return {
...auditFuncs,
...siteFuncs,
...siteCandidateFuncs,
...organizationFuncs,
};
};
Loading
Loading