Skip to content

Commit

Permalink
Merge branch 'main' into remove-repo-url-from-example-projects
Browse files Browse the repository at this point in the history
  • Loading branch information
tnolet authored Aug 31, 2023
2 parents 8ad2845 + 7dba16a commit 0892a6e
Show file tree
Hide file tree
Showing 20 changed files with 235 additions and 22 deletions.
16 changes: 16 additions & 0 deletions examples/advanced-project-js/src/__checks__/heartbeat.check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { HeartbeatCheck } = require('checkly/constructs')

// Heartbeat checks allow you to monitor jobs or recurring tasks.
// This feature is only available on paid plans.
// Upgrade your plan to start using it https://app.checklyhq.com/new-billing
// If you're already on a paid plan, uncomment the following lines to create a heartbeat check.

/* new HeartbeatCheck('heartbeat-check-1', {
name: 'Send weekly newsletter job',
period: 1,
periodUnit: 'hours',
grace: 30,
graceUnit: 'minutes',
})
*/

16 changes: 16 additions & 0 deletions examples/advanced-project/src/__checks__/heartbeat.check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { HeartbeatCheck } from 'checkly/constructs'

// Heartbeat checks allow you to monitor jobs or recurring tasks.
// This feature is only available on paid plans.
// Upgrade your plan to start using it https://app.checklyhq.com/new-billing
// If you're already on a paid plan, uncomment the following lines to create a heartbeat check.

/* new HeartbeatCheck('heartbeat-check-1', {
name: 'Send weekly newsletter job',
period: 1,
periodUnit: 'hours',
grace: 30,
graceUnit: 'minutes',
})
*/

16 changes: 16 additions & 0 deletions examples/boilerplate-project-js/__checks__/heartbeat.check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { HeartbeatCheck } = require('checkly/constructs')

// Heartbeat checks allow you to monitor jobs or recurring tasks.
// This feature is only available on paid plans.
// Upgrade your plan to start using it https://app.checklyhq.com/new-billing
// If you're already on a paid plan, uncomment the following lines to create a heartbeat check.

/* new HeartbeatCheck('heartbeat-check-1', {
name: 'Send weekly newsletter job',
period: 1,
periodUnit: 'hours',
grace: 30,
graceUnit: 'minutes',
})
*/

17 changes: 12 additions & 5 deletions examples/boilerplate-project/__checks__/heartbeat.check.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { HeartbeatCheck } from 'checkly/constructs'

new HeartbeatCheck('heartbeat-check-1', {
// Heartbeat checks allow you to monitor jobs or recurring tasks.
// This feature is only available on paid plans.
// Upgrade your plan to start using it https://app.checklyhq.com/new-billing
// If you're already on a paid plan, uncomment the following lines to create a heartbeat check.

/* new HeartbeatCheck('heartbeat-check-1', {
name: 'Send weekly newsletter job',
period: 7,
periodUnit: 'days',
grace: 2,
graceUnit: 'hours',
period: 1,
periodUnit: 'hours',
grace: 30,
graceUnit: 'minutes',
})
*/

Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ new BrowserCheck('homepage-browser-check', {
code: {
entrypoint: path.join(__dirname, 'homepage.test.ts')
},
sslCheckDomain: 'acme.com',
})
6 changes: 4 additions & 2 deletions packages/cli/e2e/__tests__/test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,23 @@ describe('test', () => {
expect(result.status).toBe(0)
})

it('Should use Github reporter', async () => {
it('Should use Github reporter and show test-session link', async () => {
const reportFilename = './reports/checkly-summary.md'
try {
fs.unlinkSync(reportFilename)
} catch {
}
const result = await runChecklyCli({
args: ['test', '--reporter', 'github'],
args: ['test', '--record', '--reporter', 'github'],
apiKey: config.get('apiKey'),
accountId: config.get('accountId'),
directory: path.join(__dirname, 'fixtures', 'test-project'),
env: { CHECKLY_REPORTER_GITHUB_OUTPUT: reportFilename },
timeout: 120000, // 2 minutes
})
expect(result.stdout).toContain('Github summary saved in')
expect(result.stdout).toContain('Detailed session summary at')
expect(result.stdout).toContain('https://chkly.link/l')
expect(fs.existsSync(path.join(__dirname, 'fixtures', 'test-project', reportFilename))).toBe(true)
expect(result.status).toBe(0)
})
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ export default class Deploy extends AuthCommand {
if (!preview) {
await ux.wait(500)
this.log(`Successfully deployed project "${project.name}" to account "${account.name}".`)

// Print the ping URL for heartbeat checks.
const heartbeatLogicalIds = project.getHeartbeatLogicalIds()
const heartbeatCheckIds = data.diff.filter((check) => heartbeatLogicalIds.includes(check.logicalId))
.map(check => check?.physicalId)

heartbeatCheckIds.forEach(async (id) => {
const { data: { pingUrl, name } } = await api.heartbeatCheck.get(id as string)
this.log(`Ping URL of heartbeat check ${chalk.green(name)} is ${chalk.italic.underline.blue(pingUrl)}.`)
})
}
} catch (err: any) {
if (err?.response?.status === 400) {
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/constructs/browser-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export interface BrowserCheckProps extends CheckProps {
* with the Puppeteer or Playwright frameworks.
*/
code: Content|Entrypoint
/**
* A valid fully qualified domain name (FQDN) to check for SSL certificate
* expiration. For example, 'app.checklyhq.com'.
*/
sslCheckDomain?: string
}

/**
Expand All @@ -30,6 +35,7 @@ export class BrowserCheck extends Check {
script: string
scriptPath?: string
dependencies?: Array<CheckDependency>
sslCheckDomain?: string

/**
* Constructs the Browser Check instance
Expand All @@ -44,6 +50,7 @@ export class BrowserCheck extends Check {
}
BrowserCheck.applyDefaultBrowserCheckConfig(props)
super(logicalId, props)
this.sslCheckDomain = props.sslCheckDomain
if ('content' in props.code) {
const script = props.code.content
this.script = script
Expand Down Expand Up @@ -127,6 +134,7 @@ export class BrowserCheck extends Check {
script: this.script,
scriptPath: this.scriptPath,
dependencies: this.dependencies,
sslCheckDomain: this.sslCheckDomain || null, // empty string is converted to null
}
}
}
8 changes: 8 additions & 0 deletions packages/cli/src/constructs/check-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ApiCheckDefaultConfig } from './api-check'
import { pathToPosix } from '../services/util'
import type { Region } from '..'
import type { Frequency } from './frequency'
import type { RetryStrategy } from './retry-strategy'

const defaultApiCheckDefaults: ApiCheckDefaultConfig = {
headers: [],
Expand Down Expand Up @@ -93,6 +94,10 @@ export interface CheckGroupProps {
*/
localTearDownScript?: string
apiCheckDefaults?: ApiCheckDefaultConfig
/**
* Sets a retry policy for the group. Use RetryStrategyBuilder to create a retry policy.
*/
retryStrategy?: RetryStrategy
}

/**
Expand All @@ -119,6 +124,7 @@ export class CheckGroup extends Construct {
localTearDownScript?: string
apiCheckDefaults: ApiCheckDefaultConfig
browserChecks?: BrowserCheckConfig
retryStrategy?: RetryStrategy

static readonly __checklyType = 'check-group'

Expand Down Expand Up @@ -156,6 +162,7 @@ export class CheckGroup extends Construct {
this.alertChannels = props.alertChannels ?? []
this.localSetupScript = props.localSetupScript
this.localTearDownScript = props.localTearDownScript
this.retryStrategy = props.retryStrategy
// `browserChecks` is not a CheckGroup resource property. Not present in synthesize()
this.browserChecks = props.browserChecks
const fileAbsolutePath = Session.checkFileAbsolutePath!
Expand Down Expand Up @@ -247,6 +254,7 @@ export class CheckGroup extends Construct {
localTearDownScript: this.localTearDownScript,
apiCheckDefaults: this.apiCheckDefaults,
environmentVariables: this.environmentVariables,
retryStrategy: this.retryStrategy,
}
}
}
8 changes: 8 additions & 0 deletions packages/cli/src/constructs/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { Region } from '..'
import type { CheckGroup } from './check-group'
import { PrivateLocation } from './private-location'
import { PrivateLocationCheckAssignment } from './private-location-check-assignment'
import { RetryStrategy } from './retry-strategy'

export interface CheckProps {
/**
Expand Down Expand Up @@ -80,6 +81,10 @@ export interface CheckProps {
* Determines if the check is available only when 'test' runs (not included when 'deploy' is executed).
*/
testOnly?: boolean
/**
* Sets a retry policy for the check. Use RetryStrategyBuilder to create a retry policy.
*/
retryStrategy?: RetryStrategy
}

// This is an abstract class. It shouldn't be used directly.
Expand All @@ -99,6 +104,7 @@ export abstract class Check extends Construct {
groupId?: Ref
alertChannels?: Array<AlertChannel>
testOnly?: boolean
retryStrategy?: RetryStrategy
__checkFilePath?: string // internal variable to filter by check file name from the CLI

static readonly __checklyType = 'check'
Expand Down Expand Up @@ -134,6 +140,7 @@ export abstract class Check extends Construct {
// alertSettings, useGlobalAlertSettings, groupId, groupOrder

this.testOnly = props.testOnly ?? false
this.retryStrategy = props.retryStrategy
this.__checkFilePath = Session.checkFilePath
}

Expand Down Expand Up @@ -209,6 +216,7 @@ export abstract class Check extends Construct {
frequencyOffset: this.frequencyOffset,
groupId: this.groupId,
environmentVariables: this.environmentVariables,
retryStrategy: this.retryStrategy,
}
}
}
1 change: 1 addition & 0 deletions packages/cli/src/constructs/heartbeat-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export class HeartbeatCheck extends Check {
}

Session.registerConstruct(this)
this.addSubscriptions()
}

synthesize (): any | null {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/constructs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export * from './private-location-group-assignment'
export * from './check'
export * from './dashboard'
export * from './phone-call-alert-channel'
export * from './retry-strategy'
9 changes: 8 additions & 1 deletion packages/cli/src/constructs/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ValidationError } from './validator-error'
import type { Runtime } from '../rest/runtimes'
import {
Check, AlertChannelSubscription, AlertChannel, CheckGroup, MaintenanceWindow, Dashboard,
PrivateLocation, PrivateLocationCheckAssignment, PrivateLocationGroupAssignment,
PrivateLocation, HeartbeatCheck, PrivateLocationCheckAssignment, PrivateLocationGroupAssignment,
} from './'
import { ResourceSync } from '../rest/projects'
import { PrivateLocationApi } from '../rest/private-locations'
Expand Down Expand Up @@ -111,6 +111,13 @@ export class Project extends Construct {
.filter((construct: Construct) => construct instanceof Check && construct.testOnly))
}

getHeartbeatLogicalIds (): string[] {
return Object
.values(this.data.check)
.filter((construct: Construct) => construct instanceof HeartbeatCheck)
.map((construct: Check) => construct.logicalId)
}

private synthesizeRecord (record: Record<string,
Check|CheckGroup|AlertChannel|AlertChannelSubscription|MaintenanceWindow|Dashboard|
PrivateLocation|PrivateLocationCheckAssignment|PrivateLocationGroupAssignment>, addTestOnly = true) {
Expand Down
67 changes: 67 additions & 0 deletions packages/cli/src/constructs/retry-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
export type RetryStrategyType = 'LINEAR' | 'EXPONENTIAL' | 'FIXED'

export interface RetryStrategy {
type: RetryStrategyType,
/**
* The number of seconds to wait before the first retry attempt.
*/
baseBackoffSeconds?: number,
/**
* The maximum number of attempts to retry the check. Value must be between 1 and 10.
*/
maxAttempts?: number,
/**
* The total amount of time to continue retrying the check (maximum 600 seconds).
*/
maxDurationSeconds?: number,
/**
* Whether retries should be run in the same region as the initial check run.
*/
sameRegion?: boolean,
}

export type RetryStrategyOptions = Pick<RetryStrategy, 'baseBackoffSeconds' | 'maxAttempts' | 'maxDurationSeconds' | 'sameRegion'>

export class RetryStrategyBuilder {
private static readonly DEFAULT_BASE_BACKOFF_SECONDS = 60
private static readonly DEFAULT_MAX_ATTEMPTS = 2
private static readonly DEFAULT_MAX_DURATION_SECONDS = 60 * 10
private static readonly DEFAULT_SAME_REGION = false

/**
* Each retry is run with the same backoff between attempts.
*/
static fixedStrategy (options: RetryStrategyOptions): RetryStrategy {
return RetryStrategyBuilder.retryStrategy('FIXED', options)
}

/**
* The delay between retries increases linearly
*
* The delay between retries is calculated using `baseBackoffSeconds * attempt`.
* For example, retries will be run with a backoff of 10s, 20s, 30s, and so on.
*/
static linearStrategy (options: RetryStrategyOptions): RetryStrategy {
return RetryStrategyBuilder.retryStrategy('LINEAR', options)
}

/**
* The delay between retries increases exponentially
*
* The delay between retries is calculated using `baseBackoffSeconds ^ attempt`.
* For example, retries will be run with a backoff of 10s, 100s, 1000s, and so on.
*/
static exponentialStrategy (options: RetryStrategyOptions): RetryStrategy {
return RetryStrategyBuilder.retryStrategy('EXPONENTIAL', options)
}

private static retryStrategy (type: RetryStrategyType, options: RetryStrategyOptions): RetryStrategy {
return {
type,
baseBackoffSeconds: options.baseBackoffSeconds ?? RetryStrategyBuilder.DEFAULT_BASE_BACKOFF_SECONDS,
maxAttempts: options.maxAttempts ?? RetryStrategyBuilder.DEFAULT_MAX_ATTEMPTS,
maxDurationSeconds: options.maxDurationSeconds ?? RetryStrategyBuilder.DEFAULT_MAX_DURATION_SECONDS,
sameRegion: options.sameRegion ?? RetryStrategyBuilder.DEFAULT_SAME_REGION,
}
}
}
2 changes: 2 additions & 0 deletions packages/cli/src/reporters/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,7 @@ export default class GithubReporter extends AbstractListReporter {
fs.writeFileSync(summaryFilename, markDown)

printLn(`Github summary saved in '${path.resolve(summaryFilename)}'.`, 2)

this._printTestSessionsUrl()
}
}
Loading

0 comments on commit 0892a6e

Please sign in to comment.