Skip to content

Commit e677f76

Browse files
authored
feat: release v1
2 parents fa2bdd3 + 2777ad5 commit e677f76

File tree

11 files changed

+118
-36
lines changed

11 files changed

+118
-36
lines changed

.projen/tasks.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.projenrc.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { awscdk, javascript } from 'projen';
22
import { ArrowParens, NpmAccess, TrailingComma } from 'projen/lib/javascript';
33

44
const project = new awscdk.AwsCdkConstructLibrary({
5+
majorVersion: 1,
56
projenrcTs: true,
67
name: 'serverless-website-analytics',
78
author: 'rehanvdm',

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,51 @@ the date will be stored after about 1min ± 1min.
239239
Location data is obtained by looking the IP address up in the [MaxMind GeoLite2](https://dev.maxmind.com/geoip/geoip2/geolite2/) database.
240240
We don't store any Personally Identifiable Information (PII) in the logs or S3, the IP address is never stored.
241241

242+
# Upgrading
243+
244+
## From V0 to V1
245+
246+
This upgrade brings two breaking changes:
247+
1. Daily partitions, querying is not backwards compatible. The data is still there, it is just in
248+
a different location so the dashboard will look empty after migrating.
249+
2. A change of Route53 record construct IDs that need manual intervention (only if you specified the `domains` property)
250+
251+
Install the new version:
252+
```
253+
npm install npm install serverless-website-analytics@~1
254+
```
255+
256+
### Data "loss" because of S3 path changes to accommodate daily partitions
257+
258+
Data will seem lost after upgrading to V1 because of the S3 path changes to accommodate daily partitions. The data is
259+
still there, it is just in a different location. The backend won't know about the old location and only use the new location
260+
so your dashboard will look empty after migrating. You can possibly run an Athena CTAS query to migrate the data to the new
261+
location, but it would need to be crafted carefully. If this is really important for you, please create a ticket and I can
262+
see if I can help.
263+
264+
### Recreate the old Route53 records (only if you specified the `domains' property)
265+
266+
This is because we needed to change the CDK construct IDs of the Route53 records and Route53 can not create duplicate
267+
record names. See issue: https://github.com/rehanvdm/serverless-website-analytics/issues/26
268+
269+
There will be some downtime, it should be less than 10 minutes. If downtime is not acceptable then use CDK escape
270+
hatches to hardcode the Route53 record IDs of your existing constructs.
271+
272+
**IMPORTANT: Take note of the names and values of these DNS records as we need to recreate them manually after deleting them.**
273+
274+
Order of operation:
275+
1. Delete DNS records with AWS CLI/Console
276+
1.1 Delete the A record pointing to your CloudFront as defined by the `domain.name` property.
277+
1.2 Optional, if using `auth.cognito` delete the Cognito login A record as well, which is defined as: `{auth.cognito.loginSubDomain}.{domain.name}`
278+
2. CDK deploy
279+
3. Recreate the DNS records with AWS CLI/Console that you deleted in step 1.
280+
281+
If you do not delete them before upgrading, you will get one of these errors in CloudFormation and it will roll back.
282+
```
283+
[Tried to create resource record set [name='analytics.rehanvdm.com.', type='A'] but it already exists]
284+
[Tried to create resource record set [name='login.analytics.rehanvdm.com.', type='A'] but it already exists]
285+
```
286+
242287
## Sponsors
243288

244289
Proudly sponsored by:

docs/API.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,51 @@ the date will be stored after about 1min ± 1min.
239239
Location data is obtained by looking the IP address up in the [MaxMind GeoLite2](https://dev.maxmind.com/geoip/geoip2/geolite2/) database.
240240
We don't store any Personally Identifiable Information (PII) in the logs or S3, the IP address is never stored.
241241

242+
# Upgrading
243+
244+
## From V0 to V1
245+
246+
This upgrade brings two breaking changes:
247+
1. Daily partitions, querying is not backwards compatible. The data is still there, it is just in
248+
a different location so the dashboard will look empty after migrating.
249+
2. A change of Route53 record construct IDs that need manual intervention (only if you specified the `domains` property)
250+
251+
Install the new version:
252+
```
253+
npm install npm install serverless-website-analytics@~1
254+
```
255+
256+
### Data "loss" because of S3 path changes to accommodate daily partitions
257+
258+
Data will seem lost after upgrading to V1 because of the S3 path changes to accommodate daily partitions. The data is
259+
still there, it is just in a different location. The backend won't know about the old location and only use the new location
260+
so your dashboard will look empty after migrating. You can possibly run an Athena CTAS query to migrate the data to the new
261+
location, but it would need to be crafted carefully. If this is really important for you, please create a ticket and I can
262+
see if I can help.
263+
264+
### Recreate the old Route53 records (only if you specified the `domains' property)
265+
266+
This is because we needed to change the CDK construct IDs of the Route53 records and Route53 can not create duplicate
267+
record names. See issue: https://github.com/rehanvdm/serverless-website-analytics/issues/26
268+
269+
There will be some downtime, it should be less than 10 minutes. If downtime is not acceptable then use CDK escape
270+
hatches to hardcode the Route53 record IDs of your existing constructs.
271+
272+
**IMPORTANT: Take note of the names and values of these DNS records as we need to recreate them manually after deleting them.**
273+
274+
Order of operation:
275+
1. Delete DNS records with AWS CLI/Console
276+
1.1 Delete the A record pointing to your CloudFront as defined by the `domain.name` property.
277+
1.2 Optional, if using `auth.cognito` delete the Cognito login A record as well, which is defined as: `{auth.cognito.loginSubDomain}.{domain.name}`
278+
2. CDK deploy
279+
3. Recreate the DNS records with AWS CLI/Console that you deleted in step 1.
280+
281+
If you do not delete them before upgrading, you will get one of these errors in CloudFormation and it will roll back.
282+
```
283+
[Tried to create resource record set [name='analytics.rehanvdm.com.', type='A'] but it already exists]
284+
[Tried to create resource record set [name='login.analytics.rehanvdm.com.', type='A'] but it already exists]
285+
```
286+
242287
## Sponsors
243288

244289
Proudly sponsored by:

src/backendAnalytics.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,8 @@ export function backendAnalytics(scope: Construct, name: (name: string) => strin
108108
type: 'string',
109109
},
110110
{
111-
name: 'year',
112-
type: 'int',
113-
},
114-
{
115-
name: 'month',
116-
type: 'int',
111+
name: 'page_opened_at_date',
112+
type: 'string',
117113
},
118114
],
119115
storageDescriptor: {
@@ -128,7 +124,9 @@ export function backendAnalytics(scope: Construct, name: (name: string) => strin
128124
},
129125
parameters: {
130126
'storage.location.template':
131-
's3://' + analyticsBucket.bucketName + '/page_views/site=${site}/year=${year}/month=${month}',
127+
's3://' +
128+
analyticsBucket.bucketName +
129+
'/page_views/site=${site}/page_opened_at_date=${page_opened_at_date}',
132130
},
133131
columns: [
134132
{
@@ -207,12 +205,11 @@ export function backendAnalytics(scope: Construct, name: (name: string) => strin
207205
},
208206
parameters: {
209207
'projection.enabled': 'true',
210-
'projection.year.type': 'integer',
211-
'projection.year.range': '2023,3023',
212-
'projection.year.interval': '1',
213-
'projection.month.type': 'integer',
214-
'projection.month.range': '1,12',
215-
'projection.month.interval': '1',
208+
'projection.page_opened_at_date.type': 'date',
209+
'projection.page_opened_at_date.format': 'yyyy-MM-dd',
210+
'projection.page_opened_at_date.interval': '1',
211+
'projection.page_opened_at_date.interval.unit': 'DAYS',
212+
'projection.page_opened_at_date.range': '2023-01-01,NOW',
216213
'projection.site.type': 'enum',
217214
'projection.site.values': props.sites.join(','),
218215
},
@@ -231,7 +228,7 @@ export function backendAnalytics(scope: Construct, name: (name: string) => strin
231228
bucketArn: analyticsBucket.bucketArn,
232229
roleArn: firehoseDeliveryRole.roleArn,
233230
prefix:
234-
'page_views/site=!{partitionKeyFromQuery:site}/year=!{partitionKeyFromQuery:year}/month=!{partitionKeyFromQuery:month}/',
231+
'page_views/site=!{partitionKeyFromQuery:site}/page_opened_at_date=!{partitionKeyFromQuery:page_opened_at_date}/',
235232
errorOutputPrefix: 'error/!{firehose:error-output-type}/',
236233
bufferingHints: {
237234
intervalInSeconds: props.firehoseBufferInterval ?? defaultFirehoseBufferInterval,
@@ -250,7 +247,7 @@ export function backendAnalytics(scope: Construct, name: (name: string) => strin
250247
parameters: [
251248
{
252249
parameterName: 'MetadataExtractionQuery',
253-
parameterValue: '{site: .site,' + ' year: .year,' + ' month: .month}',
250+
parameterValue: '{site: .site, page_opened_at_date: .page_opened_at_date}',
254251
},
255252
//Required as property it seems
256253
{

src/frontend.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ export function frontend(
187187
/* First CloudFront A record if we can */
188188
let cloudFrontRecord: route53.ARecord | undefined = undefined;
189189
if (props.domain.hostedZone) {
190-
cloudFrontRecord = new route53.ARecord(scope, 'cloudfront-record', {
190+
cloudFrontRecord = new route53.ARecord(scope, name('cloudfront-record'), {
191191
recordName: props.domain.name,
192192
target: route53.RecordTarget.fromAlias(new route53Targets.CloudFrontTarget(frontendDist)),
193193
zone: props.domain.hostedZone,
@@ -218,7 +218,7 @@ export function frontend(
218218

219219
/* Add the Cognito A record if we can */
220220
if (props.domain.hostedZone) {
221-
new route53.ARecord(scope, 'custom-domain-dns-record', {
221+
new route53.ARecord(scope, name('custom-domain-dns-record'), {
222222
recordName: props.auth.cognito.loginSubDomain,
223223
target: route53.RecordTarget.fromAlias({
224224
bind: () => ({

src/src/backend/api-ingest/v1/page/view/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,16 @@ export function pageView(trpcInstance: TrpcInstance) {
4747
);
4848
const referrer = getCleanedUpReferrer(input.site, input.referrer);
4949
const pageOpenedAt = DateUtils.parseIso(input.page_opened_at);
50+
const pageOpenedAtDate = DateUtils.stringifyFormat(pageOpenedAt, 'yyyy-MM-dd');
51+
5052
const data: Page = {
5153
site: input.site,
52-
year: pageOpenedAt.getFullYear(),
53-
month: pageOpenedAt.getMonth() + 1,
54-
5554
user_id: input.user_id,
5655
session_id: input.session_id,
5756
page_id: input.page_id,
5857
page_url: input.page_url,
5958
page_opened_at: input.page_opened_at,
59+
page_opened_at_date: pageOpenedAtDate,
6060
time_on_page: input.time_on_page,
6161
country_iso: countryIso,
6262
country_name: countryName,

src/src/backend/lib/dal/athena/page_views.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,8 @@ export class AthenaPageViews extends AthenaBase {
2525
* @param sites
2626
*/
2727
cteFilteredDataQuery(columns: string[], fromDate: Date, toDate: Date, sites: string[]) {
28-
const months = DateUtils.getMonthsBetweenDates(fromDate, toDate);
29-
const cteWhereClauseDates: string = months
30-
// +1 to the month because months are 0-indexed in JS but Athena/Firehose is 1-indexed
31-
.map((month) => `(year = ${month.getFullYear()} AND month = ${month.getMonth() + 1})`)
32-
.join(' OR ');
3328
const cteWhereClauseSites = sites.map((site) => `site = '${site}'`).join(' OR ');
34-
35-
const cteWhereClause = `(${cteWhereClauseSites}) AND (${cteWhereClauseDates})`;
29+
const cteWhereClause = `(${cteWhereClauseSites})`;
3630
const exactTimeFrom = DateUtils.stringifyFormat(fromDate, 'yyyy-MM-dd HH:mm:ss.SSS');
3731
const exactTimeTo = DateUtils.stringifyFormat(toDate, 'yyyy-MM-dd HH:mm:ss.SSS');
3832

src/src/backend/lib/models/page.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ import { z } from 'zod';
22

33
export const SchemaPage = z.object({
44
site: z.string(),
5-
year: z.number(),
6-
month: z.number(),
75
user_id: z.string(),
86
session_id: z.string(),
97
page_id: z.string(),
108
page_url: z.string(),
119
page_opened_at: z.string(),
10+
page_opened_at_date: z.string().optional(),
1211
time_on_page: z.number(),
1312
country_iso: z.string().optional(),
1413
country_name: z.string().optional(),

src/src/tests/backend/api-front/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,9 @@ describe('API Frontend', function () {
196196
// from: new Date(2023, 3, 1, 0, 0, 0).toISOString(),
197197
// to: new Date(2023, 3, 30, 23, 59, 59,999).toISOString(),
198198

199-
// Month 4 - April - Past so data fixed, not changing still
200-
from: new Date(2023, 3, 1, 0, 0, 0).toISOString(),
201-
to: new Date(2023, 3, 22, 23, 59, 59, 999).toISOString(),
199+
// Month 5 - June
200+
from: new Date(2023, 5, 1, 0, 0, 0).toISOString(),
201+
to: new Date(2023, 5, 22, 23, 59, 59, 999).toISOString(),
202202
},
203203
authHeaders
204204
);

0 commit comments

Comments
 (0)