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

Initial import #4

Merged
merged 3 commits into from
Jan 24, 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
6 changes: 3 additions & 3 deletions .github/workflows/ts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ jobs:
node-version: 20
cache: yarn
- run: yarn
- run: yarn test
- run: yarn test --reporters=default --reporters=jest-junit
- run: yarn build
- run: yarn package

- name: e2e-test
- name: int128/datadog-test-report-action
uses: ./
with:
name: foo
junit-xml-path: junit.xml

generate:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,6 @@ lib/

# Only release tag contains dist directory
/dist

# Test reports
junit.xml
94 changes: 11 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,103 +1,31 @@
# typescript-action [![ts](https://github.com/int128/typescript-action/actions/workflows/ts.yaml/badge.svg)](https://github.com/int128/typescript-action/actions/workflows/ts.yaml)

This is a template of TypeScript action.
Inspired from https://github.com/actions/typescript-action.


## Features

- Ready to develop with the minimum configs
- Yarn
- Prettier
- ESLint
- tsconfig
- Jest
- Automated continuous release
- Keep consistency of generated files
- Shipped with Renovate config
# datadog-test-report-action [![ts](https://github.com/int128/datadog-test-report-action/actions/workflows/ts.yaml/badge.svg)](https://github.com/int128/datadog-test-report-action/actions/workflows/ts.yaml)

TODO

## Getting Started

Click `Use this template` to create a repository.

An initial release `v0.0.0` is automatically created by GitHub Actions.
You can see the generated files in `dist` directory on the tag.

Then checkout your repository and test it. Node.js is required.

```console
$ git clone https://github.com/your/repo.git

$ yarn
$ yarn test
```

Create a pull request for a change.

```console
$ git checkout -b feature
$ git commit -m 'Add feature'
$ gh pr create -fd
```

Once you merge a pull request, a new minor release (such as `v0.1.0`) is created.


### Stable release

When you want to create a stable release, change the major version in [release workflow](.github/workflows/release.yaml).

```yaml
- uses: int128/release-typescript-action@v1
with:
major-version: 1
```

Then a new stable release `v1.0.0` is created.


## Specification

To run this action, create a workflow as follows:

```yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: int128/typescript-action@v1
- uses: int128/datadog-test-report-action@v1
with:
name: hello
```

### Inputs

| Name | Default | Description
|------|----------|------------
| `name` | (required) | example input

| Name | Default | Description |
| ----------------- | ---------- | ------------------------------------- |
| `junit-xml-path` | (required) | Glob pattern to the JUnit XML file(s) |
| `datadog-api-key` | - | Datadog API key |
| `datadog-site` | - | Datadog site |

### Outputs

| Name | Description
|------|------------
| `example` | example output


## Development

### Release workflow

When a pull request is merged into main branch, a new minor release is created by GitHub Actions.
See https://github.com/int128/release-typescript-action for details.

### Keep consistency of generated files

If a pull request needs to be fixed by Prettier, an additional commit to fix it will be added by GitHub Actions.
See https://github.com/int128/update-generated-files-action for details.

### Dependency update

You can enable Renovate to update the dependencies.
This repository is shipped with the config https://github.com/int128/typescript-action-renovate-config.
| Name | Description |
| --------- | -------------- |
| `example` | example output |
14 changes: 10 additions & 4 deletions action.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
name: typescript-action
description: Template of TypeScript Action
name: datadog-test-report-action
description: Send test reports to Datadog

inputs:
name:
description: example input
junit-xml-path:
description: Glob pattern to the JUnit XML file(s)
required: true
datadog-api-key:
description: Datadog API key
required: false
datadog-site:
description: Datadog site
required: false

runs:
using: 'node20'
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
"test": "jest"
},
"dependencies": {
"@actions/core": "1.10.1"
"@actions/core": "1.10.1",
"@actions/glob": "^0.4.0",
"@datadog/datadog-api-client": "^1.21.0",
"fast-xml-parser": "^4.3.3"
},
"devDependencies": {
"@tsconfig/node20": "20.1.2",
Expand All @@ -22,6 +25,7 @@
"eslint": "8.56.0",
"eslint-plugin-jest": "27.6.3",
"jest": "29.7.0",
"jest-junit": "^16.0.0",
"prettier": "3.2.4",
"ts-jest": "29.1.1",
"typescript": "5.3.3"
Expand Down
70 changes: 70 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as core from '@actions/core'
import { client, v1 } from '@datadog/datadog-api-client'

type Inputs = {
datadogApiKey?: string
datadogSite?: string
}

export type MetricsClient = {
submitMetrics: (series: v1.Series[], description: string) => Promise<void>
submitDistributionPoints(series: v1.DistributionPointsSeries[], description: string): Promise<void>
}

class DryRunMetricsClient implements MetricsClient {
// eslint-disable-next-line @typescript-eslint/require-await
async submitMetrics(series: v1.Series[], description: string): Promise<void> {
core.startGroup(`Metrics payload (dry-run) (${description})`)
core.info(JSON.stringify(series, undefined, 2))
core.endGroup()
}

// eslint-disable-next-line @typescript-eslint/require-await
async submitDistributionPoints(series: v1.DistributionPointsSeries[], description: string): Promise<void> {
core.startGroup(`Distribution points payload (dry-run) (${description})`)
core.info(JSON.stringify(series, undefined, 2))
core.endGroup()
}
}

class RealMetricsClient implements MetricsClient {
constructor(private readonly metricsApi: v1.MetricsApi) {}

async submitMetrics(series: v1.Series[], description: string): Promise<void> {
core.startGroup(`Metrics payload (${description})`)
core.info(JSON.stringify(series, undefined, 2))
core.endGroup()

core.info(`Sending ${series.length} metrics to Datadog`)
const accepted = await this.metricsApi.submitMetrics({ body: { series } })
core.info(`Sent ${JSON.stringify(accepted)}`)
}

async submitDistributionPoints(series: v1.DistributionPointsSeries[], description: string): Promise<void> {
core.startGroup(`Distribution points payload (${description})`)
core.info(JSON.stringify(series, undefined, 2))
core.endGroup()

core.info(`Sending ${series.length} distribution points to Datadog`)
const accepted = await this.metricsApi.submitDistributionPoints({ body: { series } })
core.info(`Sent ${JSON.stringify(accepted)}`)
}
}

export const createMetricsClient = (inputs: Inputs): MetricsClient => {
if (!inputs.datadogApiKey) {
return new DryRunMetricsClient()
}

const configuration = client.createConfiguration({
authMethods: {
apiKeyAuth: inputs.datadogApiKey,
},
})
if (inputs.datadogSite) {
client.setServerVariables(configuration, {
site: inputs.datadogSite,
})
}
return new RealMetricsClient(new v1.MetricsApi(configuration))
}
116 changes: 116 additions & 0 deletions src/junitxml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import assert from 'assert'
import { XMLParser } from 'fast-xml-parser'

export type JunitXml = {
testsuites?: {
testsuite?: TestSuite[]
}
testsuite?: TestSuite[]
}

function assertJunitXml(x: unknown): asserts x is JunitXml {
assert(typeof x === 'object')
assert(x != null)

if ('testsuites' in x) {
assert(typeof x.testsuites === 'object')
assert(x.testsuites != null)

if ('testsuite' in x.testsuites) {
assert(Array.isArray(x.testsuites.testsuite))
for (const testsuite of x.testsuites.testsuite) {
assertTestSuite(testsuite)
}
}
}

if ('testsuite' in x) {
assert(Array.isArray(x.testsuite))
for (const testsuite of x.testsuite) {
assertTestSuite(testsuite)
}
}
}

export type TestSuite = {
'@_name': string
'@_time': number
testsuite?: TestSuite[]
testcase?: TestCase[]
}

function assertTestSuite(x: unknown): asserts x is TestSuite {
assert(typeof x === 'object')
assert(x != null)
assert('@_name' in x)
assert(typeof x['@_name'] === 'string')
assert('@_time' in x)
assert(typeof x['@_time'] === 'number')

if ('testsuite' in x) {
assert(Array.isArray(x.testsuite))
for (const testsuite of x.testsuite) {
assertTestSuite(testsuite)
}
}
if ('testcase' in x) {
assert(Array.isArray(x.testcase))
for (const testcase of x.testcase) {
assertTestCase(testcase)
}
}
}

export type TestCase = {
'@_name': string
'@_time': number
'@_classname'?: string
'@_file'?: string
failure?: {
'@_message'?: string
}
}

function assertTestCase(x: unknown): asserts x is TestCase {
assert(typeof x === 'object')
assert(x != null)
assert('@_name' in x)
assert(typeof x['@_name'] === 'string')
assert('@_time' in x)
assert(typeof x['@_time'] === 'number')

if ('@_classname' in x) {
assert(typeof x['@_classname'] === 'string')
}
if ('@_file' in x) {
assert(typeof x['@_file'] === 'string')
}
if ('failure' in x) {
assert(typeof x.failure === 'object')
assert(x.failure != null)
if ('@_message' in x.failure) {
assert(typeof x.failure['@_message'] === 'string')
}
}
}

export const parseJunitXml = (xml: string | Buffer): JunitXml => {
const parser = new XMLParser({
ignoreAttributes: false,
removeNSPrefix: true,
isArray: (_: string, jPath: string): boolean => {
const elementName = jPath.split('.').pop()
return ['testsuite', 'testcase'].includes(elementName ?? '')
},
attributeValueProcessor: (attrName: string, attrValue: string, jPath: string) => {
const elementName = jPath.split('.').pop()
if (attrName === 'time' && ['testsuites', 'testsuite', 'testcase'].includes(elementName ?? '')) {
return Number(attrValue)
}
return attrValue
},
})
const parsed: unknown = parser.parse(xml)
assertJunitXml(parsed)
return parsed
}
4 changes: 3 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { run } from './run'

const main = async (): Promise<void> => {
await run({
name: core.getInput('name', { required: true }),
junitXmlPath: core.getInput('junit-xml-path', { required: true }),
datadogApiKey: core.getInput('datadog-api-key'),
datadogSite: core.getInput('datadog-site'),
})
}

Expand Down
Loading