Ingest code coverage (#62336)

This commit is contained in:
Tre 2020-05-13 15:09:42 -06:00 committed by GitHub
parent 30e305eb00
commit 4853f24705
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 28300 additions and 81 deletions

View file

@ -5,89 +5,26 @@ kibanaLibrary.load() // load from the Jenkins instance
kibanaPipeline(timeoutMinutes: 240) {
catchErrors {
def timestamp = new Date(currentBuild.startTimeInMillis).format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC"))
withEnv([
'CODE_COVERAGE=1', // Needed for multiple ci scripts, such as remote.ts, test/scripts/*.sh, schema.js, etc.
"TIME_STAMP=${timestamp}",
'CODE_COVERAGE=1', // Enables coverage. Needed for multiple ci scripts, such as remote.ts, test/scripts/*.sh, schema.js, etc.
]) {
parallel([
'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'),
'x-pack-intake-agent': {
withEnv([
'NODE_ENV=test' // Needed for jest tests only
]) {
workers.intake('x-pack-intake', './test/scripts/jenkins_xpack.sh')()
}
},
'kibana-oss-agent': workers.functional('kibana-oss-tests', { kibanaPipeline.buildOss() }, [
'oss-ciGroup1': kibanaPipeline.ossCiGroupProcess(1),
'oss-ciGroup2': kibanaPipeline.ossCiGroupProcess(2),
'oss-ciGroup3': kibanaPipeline.ossCiGroupProcess(3),
'oss-ciGroup4': kibanaPipeline.ossCiGroupProcess(4),
'oss-ciGroup5': kibanaPipeline.ossCiGroupProcess(5),
'oss-ciGroup6': kibanaPipeline.ossCiGroupProcess(6),
'oss-ciGroup7': kibanaPipeline.ossCiGroupProcess(7),
'oss-ciGroup8': kibanaPipeline.ossCiGroupProcess(8),
'oss-ciGroup9': kibanaPipeline.ossCiGroupProcess(9),
'oss-ciGroup10': kibanaPipeline.ossCiGroupProcess(10),
'oss-ciGroup11': kibanaPipeline.ossCiGroupProcess(11),
'oss-ciGroup12': kibanaPipeline.ossCiGroupProcess(12),
]),
'kibana-xpack-agent': workers.functional('kibana-xpack-tests', { kibanaPipeline.buildXpack() }, [
'xpack-ciGroup1': kibanaPipeline.xpackCiGroupProcess(1),
'xpack-ciGroup2': kibanaPipeline.xpackCiGroupProcess(2),
'xpack-ciGroup3': kibanaPipeline.xpackCiGroupProcess(3),
'xpack-ciGroup4': kibanaPipeline.xpackCiGroupProcess(4),
'xpack-ciGroup5': kibanaPipeline.xpackCiGroupProcess(5),
'xpack-ciGroup6': kibanaPipeline.xpackCiGroupProcess(6),
'xpack-ciGroup7': kibanaPipeline.xpackCiGroupProcess(7),
'xpack-ciGroup8': kibanaPipeline.xpackCiGroupProcess(8),
'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9),
'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10),
]),
])
workers.base(name: 'coverage-worker', size: 'l', ramDisk: false, bootstrapped: false) {
kibanaPipeline.downloadCoverageArtifacts()
kibanaPipeline.bash(
'''
# bootstrap from x-pack folder
source src/dev/ci_setup/setup_env.sh
cd x-pack
yarn kbn bootstrap --prefer-offline
cd ..
# extract archives
mkdir -p /tmp/extracted_coverage
echo extracting intakes
tar -xzf /tmp/downloaded_coverage/coverage/kibana-intake/kibana-coverage.tar.gz -C /tmp/extracted_coverage
tar -xzf /tmp/downloaded_coverage/coverage/x-pack-intake/kibana-coverage.tar.gz -C /tmp/extracted_coverage
echo extracting kibana-oss-tests
tar -xzf /tmp/downloaded_coverage/coverage/kibana-oss-tests/kibana-coverage.tar.gz -C /tmp/extracted_coverage
echo extracting kibana-xpack-tests
tar -xzf /tmp/downloaded_coverage/coverage/kibana-xpack-tests/kibana-coverage.tar.gz -C /tmp/extracted_coverage
# replace path in json files to have valid html report
pwd=$(pwd)
du -sh /tmp/extracted_coverage/target/kibana-coverage/
echo replacing path in json files
for i in {1..9}; do
sed -i "s|/dev/shm/workspace/kibana|$pwd|g" /tmp/extracted_coverage/target/kibana-coverage/functional/${i}*.json &
done
wait
# merge oss & x-pack reports
echo merging coverage reports
yarn nyc report --temp-dir /tmp/extracted_coverage/target/kibana-coverage/jest --report-dir target/kibana-coverage/jest-combined --reporter=html --reporter=json-summary
yarn nyc report --temp-dir /tmp/extracted_coverage/target/kibana-coverage/functional --report-dir target/kibana-coverage/functional-combined --reporter=html --reporter=json-summary
echo copy mocha reports
mkdir -p target/kibana-coverage/mocha-combined
cp -r /tmp/extracted_coverage/target/kibana-coverage/mocha target/kibana-coverage/mocha-combined
''',
"run `yarn kbn bootstrap && merge coverage`"
)
sh 'tar -czf kibana-jest-coverage.tar.gz target/kibana-coverage/jest-combined/*'
kibanaPipeline.uploadCoverageArtifacts("coverage/jest-combined", 'kibana-jest-coverage.tar.gz')
sh 'tar -czf kibana-functional-coverage.tar.gz target/kibana-coverage/functional-combined/*'
kibanaPipeline.uploadCoverageArtifacts("coverage/functional-combined", 'kibana-functional-coverage.tar.gz')
sh 'tar -czf kibana-mocha-coverage.tar.gz target/kibana-coverage/mocha-combined/*'
kibanaPipeline.uploadCoverageArtifacts("coverage/mocha-combined", 'kibana-mocha-coverage.tar.gz')
kibanaCoverage.runTests()
handleIngestion(TIME_STAMP)
}
}
kibanaPipeline.sendMail()
}
kibanaPipeline.sendMail()
}
def handleIngestion(timestamp) {
kibanaPipeline.downloadCoverageArtifacts()
kibanaCoverage.prokLinks("### Process HTML Links")
kibanaCoverage.collectVcsInfo("### Collect VCS Info")
kibanaCoverage.ingest(timestamp, '### Injest && Upload')
kibanaCoverage.uploadCoverageStaticSite(timestamp)
}

3
.github/CODEOWNERS vendored
View file

@ -128,6 +128,9 @@
/src/legacy/server/utils/ @elastic/kibana-operations
/src/legacy/server/warnings/ @elastic/kibana-operations
# Quality Assurance
/src/dev/code_coverage @elastic/kibana-qa
# Platform
/src/core/ @elastic/kibana-platform
/config/kibana.yml @elastic/kibana-platform

2
.gitignore vendored
View file

@ -46,4 +46,6 @@ npm-debug.log*
.tern-project
x-pack/plugins/apm/tsconfig.json
apm.tsconfig.json
/x-pack/legacy/plugins/apm/e2e/snapshots.js
/x-pack/plugins/apm/e2e/snapshots.js
.nyc_output

View file

@ -72,7 +72,8 @@
"spec_to_console": "node scripts/spec_to_console",
"backport-skip-ci": "backport --prDescription \"[skip-ci]\"",
"storybook": "node scripts/storybook",
"cover:report": "nyc report --temp-dir target/kibana-coverage/functional --report-dir target/coverage/report --reporter=lcov && open ./target/coverage/report/lcov-report/index.html"
"cover:report": "nyc report --temp-dir target/kibana-coverage/functional --report-dir target/coverage/report --reporter=lcov && open ./target/coverage/report/lcov-report/index.html",
"cover:functional:merge": "nyc report --temp-dir target/kibana-coverage/functional --report-dir target/coverage/report/functional --reporter=json-summary"
},
"repository": {
"type": "git",

View file

@ -0,0 +1,21 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
require('../src/setup_node_env');
require('../src/dev/code_coverage/ingest_coverage').runCoverageIngestionCli();

View file

@ -0,0 +1,34 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import { ciRunUrl } from '../transforms';
describe(`Transform fn`, () => {
describe(`ciRunUrl`, () => {
it(`should add the url when present in the environment`, () => {
process.env.CI_RUN_URL = 'blah';
expect(ciRunUrl()).to.have.property('ciRunUrl', 'blah');
});
it(`should not include the url if not present in the environment`, () => {
process.env.CI_RUN_URL = void 0;
expect(ciRunUrl({ a: 'a' })).not.to.have.property('ciRunUrl');
});
});
});

View file

@ -0,0 +1,22 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export const STATIC_SITE_URL_PROP_NAME = 'staticSiteUrl';
export const COVERAGE_INDEX = process.env.COVERAGE_INDEX || 'kibana_code_coverage';
export const TOTALS_INDEX = process.env.TOTALS_INDEX || `kibana_total_code_coverage`;

View file

@ -0,0 +1,62 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint new-cap: 0 */
/* eslint no-unused-vars: 0 */
export const Right = x => ({
chain: f => f(x),
map: f => Right(f(x)),
fold: (f, g) => g(x),
inspect: () => `Right(${x})`,
});
Right.of = function of(x) {
return Right(x);
};
export function right(x) {
return Right.of(x);
}
export const Left = x => ({
chain: f => Left(x),
map: f => Left(x),
fold: (f, g) => f(x),
inspect: () => `Left(${x})`,
});
Left.of = function of(x) {
return Left(x);
};
export function left(x) {
return Left.of(x);
}
export const fromNullable = x =>
x !== null && x !== undefined && x !== false && x !== 'undefined' ? Right(x) : Left(null);
export const tryCatch = f => {
try {
return Right(f());
} catch (e) {
return Left(e);
}
};

View file

@ -0,0 +1,61 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { resolve } from 'path';
import { prok } from './process';
import { run, createFlagError } from '@kbn/dev-utils';
const ROOT = resolve(__dirname, '../../../..');
const flags = {
string: ['path', 'verbose', 'vcsInfoPath'],
help: `
--path Required, path to the file to extract coverage data
--vcsInfoPath Required, path to the git info file (branch, sha, author, & commit msg)
`,
};
export function runCoverageIngestionCli() {
run(
({ flags, log }) => {
if (flags.path === '') throw createFlagError('please provide a single --path flag');
if (flags.vcsInfoPath === '')
throw createFlagError('please provide a single --vcsInfoPath flag');
if (flags.verbose) log.verbose(`Verbose logging enabled`);
const resolveRoot = resolve.bind(null, ROOT);
const jsonSummaryPath = resolveRoot(flags.path);
const vcsInfoFilePath = resolveRoot(flags.vcsInfoPath);
prok({ jsonSummaryPath, vcsInfoFilePath }, log);
},
{
description: `
Post code coverage in json-summary format to an ES index.
Note: You probably should create the index first.
Two indexes are needed, see README.md.
Examples:
See 'ingest_code_coverage_readme.md'
`,
flags,
}
);
}

View file

@ -0,0 +1,194 @@
# Create index mapping
This is usually done in Kibana's dev tools ui.
```
"mappings" : {
"properties" : {
"@timestamp" : {
"type" : "date"
},
"BUILD_ID" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"branches" : {
"properties" : {
"covered" : {
"type" : "long"
},
"pct" : {
"type" : "long"
},
"skipped" : {
"type" : "long"
},
"total" : {
"type" : "long"
}
}
},
"ciRunUrl" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"coveredFilePath" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"functions" : {
"properties" : {
"covered" : {
"type" : "long"
},
"pct" : {
"type" : "long"
},
"skipped" : {
"type" : "long"
},
"total" : {
"type" : "long"
}
}
},
"isTotal" : {
"type" : "boolean"
},
"jsonSummaryPath" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"lines" : {
"properties" : {
"covered" : {
"type" : "long"
},
"pct" : {
"type" : "long"
},
"skipped" : {
"type" : "long"
},
"total" : {
"type" : "long"
}
}
},
"path" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"statements" : {
"properties" : {
"covered" : {
"type" : "long"
},
"pct" : {
"type" : "long"
},
"skipped" : {
"type" : "long"
},
"total" : {
"type" : "long"
}
}
},
"staticSiteUrl" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"testRunnerType" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"vcs" : {
"properties" : {
"author" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"branch" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"commitMsg" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"sha" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"vcsUrl" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
```
_The main portion of the above mapping, is the timestamp-date mapping._

View file

@ -0,0 +1,103 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const { Client } = require('@elastic/elasticsearch');
import { createFailError } from '@kbn/dev-utils';
import chalk from 'chalk';
import { green, always } from './utils';
import { fromNullable } from './either';
import { COVERAGE_INDEX, TOTALS_INDEX } from './constants';
const node = process.env.ES_HOST || 'http://localhost:9200';
const redacted = redact(node);
const client = new Client({ node });
const indexName = body => (body.isTotal ? TOTALS_INDEX : COVERAGE_INDEX);
export const ingest = log => async body => {
const index = indexName(body);
if (process.env.NODE_ENV === 'integration_test') {
log.debug(`### Just Logging, ${green('NOT actually sending')} to [${green(index)}]`);
logSuccess(log, index, body);
} else {
try {
log.debug(`### Actually sending to: ${green(index)}`);
await client.index({ index, body });
logSuccess(log, index, body);
} catch (e) {
throw createFailError(errMsg(index, body, e));
}
}
};
function logSuccess(log, index, body) {
const logShort = () => `### Sent:
### ES HOST (redacted): ${redacted}
### Index: ${green(index)}`;
logShort();
log.verbose(pretty(body));
const { staticSiteUrl } = body;
logShort();
log.debug(`### staticSiteUrl: ${staticSiteUrl}`);
}
function errMsg(index, body, e) {
const orig = fromNullable(e.body).fold(
always(''),
() => `### Orig Err:\n${pretty(e.body.error)}`
);
const red = color('red');
return `
### ES HOST (redacted): \n\t${red(redacted)}
### INDEX: \n\t${red(index)}
### Partial orig err stack: \n\t${partial(e.stack)}
### Item BODY:\n${pretty(body)}
${orig}
### Troubleshooting Hint:
${red('Perhaps the coverage data was not merged properly?\n')}
`;
}
function partial(x) {
return x
.split('\n')
.splice(0, 2)
.join('\n');
}
function redact(x) {
const url = new URL(x);
if (url.password) {
return `${url.protocol}//${url.host}`;
} else {
return x;
}
}
function color(whichColor) {
return function colorInner(x) {
return chalk[whichColor].bgWhiteBright(x);
};
}
function pretty(x) {
return JSON.stringify(x, null, 2);
}

View file

@ -0,0 +1,24 @@
# Convert Code Coverage Json Summary and Send to ES
## How it works
It starts with this jenkins pipeline file:
.ci/Jenkinsfile_coverage#L60
```
src/dev/code_coverage/shell_scripts/ingest_coverage.sh ${BUILD_NUMBER} ${env.BUILD_URL}
```
The ingestion system is hard coded to look for 3 coverage summary files...all json.
From there, an event stream is created, that massages the data to an output format in json that is ingested.
## Configuration
There is really only one config step.
The index [mapping](src/dev/code_coverage/ingest_coverage/index_mapping.md) for one of
of the indexes has to be manually created.
Currently, we just add it using Kibana's Dev Tools.

View file

@ -0,0 +1,181 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import { spawn } from 'child_process';
import { resolve } from 'path';
import { green, always } from '../utils';
import { STATIC_SITE_URL_PROP_NAME, COVERAGE_INDEX } from '../constants';
const ROOT_DIR = resolve(__dirname, '../../../../..');
const MOCKS_DIR = resolve(__dirname, './mocks');
const staticSiteUrlRegexes = {
staticHostIncluded: /https:\/\/kibana-coverage\.elastic\.dev/,
timeStampIncluded: /\d{4}-\d{2}-\d{2}T\d{2}.*\d{2}.*\d{2}Z/,
folderStructureIncluded: /(?:.*|.*-combined)\//,
};
const env = {
BUILD_ID: 407,
CI_RUN_URL: 'https://kibana-ci.elastic.co/job/elastic+kibana+code-coverage/407/',
STATIC_SITE_URL_BASE: 'https://kibana-coverage.elastic.dev',
TIME_STAMP: '2020-03-02T21:11:47Z',
ES_HOST: 'https://super:changeme@some.fake.host:9243',
NODE_ENV: 'integration_test',
COVERAGE_INGESTION_KIBANA_ROOT: '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana',
};
const includesSiteUrlPredicate = x => x.includes(STATIC_SITE_URL_PROP_NAME);
const siteUrlLines = specificLinesOnly(includesSiteUrlPredicate);
const splitByNewLine = x => x.split('\n');
const siteUrlsSplitByNewLine = siteUrlLines(splitByNewLine);
const siteUrlsSplitByNewLineWithoutBlanks = siteUrlsSplitByNewLine(notBlankLines);
describe('Ingesting Coverage to Cluster', () => {
const verboseArgs = [
'scripts/ingest_coverage.js',
'--verbose',
'--vcsInfoPath',
'src/dev/code_coverage/ingest_coverage/integration_tests/mocks/VCS_INFO.txt',
'--path',
];
const noTotalsPath = 'jest-combined/coverage-summary-NO-total.json';
const bothIndexesPath = 'jest-combined/coverage-summary-manual-mix.json';
describe('with NODE_ENV set to "integration_test"', () => {
describe(`and debug || verbose turned on`, () => {
describe(`to the [${COVERAGE_INDEX}] index`, () => {
const mutableCoverageIndexChunks = [];
beforeAll(done => {
const ingestAndMutateAsync = ingestAndMutate(done);
const ingestAndMutateAsyncWithPath = ingestAndMutateAsync(noTotalsPath);
const verboseIngestAndMutateAsyncWithPath = ingestAndMutateAsyncWithPath(verboseArgs);
verboseIngestAndMutateAsyncWithPath(mutableCoverageIndexChunks);
});
it(
'should result in every posted item having a site url that meets all regex assertions',
always(
siteUrlsSplitByNewLineWithoutBlanks(mutableCoverageIndexChunks).forEach(
expectAllRegexesToPass({
...staticSiteUrlRegexes,
endsInDotJsDotHtml: /.js.html$/,
})
)
)
);
});
describe(`to both indexes in the same push`, () => {
const mutableBothIndexesChunks = [];
beforeAll(done => {
const ingestAndMutateAsync = ingestAndMutate(done);
const ingestAndMutateAsyncWithPath = ingestAndMutateAsync(bothIndexesPath);
const verboseIngestAndMutateAsyncWithPath = ingestAndMutateAsyncWithPath(verboseArgs);
verboseIngestAndMutateAsyncWithPath(mutableBothIndexesChunks);
});
it(
'should result in every posted item having a site url that meets all regex assertions',
always(
siteUrlsSplitByNewLineWithoutBlanks(mutableBothIndexesChunks).forEach(
expectAllRegexesToPass(staticSiteUrlRegexes)
)
)
);
it('should result in the "just logging" message being present in the log', () => {
expect(mutableBothIndexesChunks.some(x => x.includes('Just Logging'))).to.be(true);
});
it('should result in the "actually sending" message NOT being present in the log', () => {
expect(mutableBothIndexesChunks.every(x => !x.includes('Actually sending...'))).to.be(
true
);
});
describe(`with provided vcs info file`, () => {
const filterZero = xs => included => xs.filter(x => x.includes(included))[0];
const filteredWith = filterZero(mutableBothIndexesChunks);
it('should have a vcs block', () => {
const vcs = 'vcs';
const portion = filteredWith(vcs);
expect(portion).to.contain(vcs);
});
it(`should have a branch`, () => {
const branch = `"branch":`;
const portion = filteredWith(branch);
expect(portion).to.contain(branch);
expect(portion).to.contain(`"origin/ingest-code-coverage"`);
});
it(`should have a sha`, () => {
const sha = `"sha":`;
const portion = filteredWith(sha);
expect(portion).to.contain(sha);
expect(portion).to.contain(`"f07b34f6206"`);
});
it(`should have an author`, () => {
const author = `"author":`;
const portion = filteredWith(author);
expect(portion).to.contain(author);
expect(portion).to.contain(`"Tre' Seymour"`);
});
it(`should have a commit msg, truncated`, () => {
const commitMsg = `"commitMsg":`;
const portion = filteredWith(commitMsg);
expect(portion).to.contain(commitMsg);
expect(portion).to.contain(`"Lorem :) ipsum Tre' λ dolor sit amet, consectetur ..."`);
});
});
});
});
});
});
function ingestAndMutate(done) {
return summaryPathSuffix => args => xs => {
const coverageSummaryPath = resolve(MOCKS_DIR, summaryPathSuffix);
const opts = [...args, coverageSummaryPath];
const ingest = spawn(process.execPath, opts, { cwd: ROOT_DIR, env });
ingest.stdout.on('data', x => xs.push(x + ''));
ingest.on('close', done);
};
}
function specificLinesOnly(predicate) {
return splitByNewLine => notBlankLines => xs =>
xs.filter(predicate).map(x => splitByNewLine(x).reduce(notBlankLines));
}
function notBlankLines(acc, item) {
if (item !== '') return item;
return acc;
}
function expectAllRegexesToPass(staticSiteUrlRegexes) {
return urlLine =>
Object.entries(staticSiteUrlRegexes).forEach(regexTuple => {
if (!regexTuple[1].test(urlLine))
throw new Error(
`\n### ${green('FAILED')}\nAsserting: [\n\t${green(
regexTuple[0]
)}\n]\nAgainst: [\n\t${urlLine}\n]`
);
});
}

View file

@ -0,0 +1,8 @@
origin/ingest-code-coverage
f07b34f6206
Tre' Seymour
Lorem :) ipsum Tre' λ dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst.

View file

@ -0,0 +1,158 @@
{
"total": {
"lines": {
"total": 14817,
"covered": 3377,
"skipped": 0,
"pct": 22.79
},
"statements": {
"total": 15507,
"covered": 3453,
"skipped": 0,
"pct": 22.27
},
"functions": {
"total": 3286,
"covered": 654,
"skipped": 0,
"pct": 19.9
},
"branches": {
"total": 8060,
"covered": 1439,
"skipped": 0,
"pct": 17.85
}
},
"/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana/x-pack/legacy/plugins/reporting/server/browsers/extract/unzip.js": {
"lines": {
"total": 4,
"covered": 4,
"skipped": 0,
"pct": 100
},
"functions": {
"total": 1,
"covered": 1,
"skipped": 0,
"pct": 100
},
"statements": {
"total": 4,
"covered": 4,
"skipped": 0,
"pct": 100
},
"branches": {
"total": 0,
"covered": 0,
"skipped": 0,
"pct": 100
}
},
"total": {
"lines": {
"total": 14817,
"covered": 3377,
"skipped": 0,
"pct": 22.79
},
"statements": {
"total": 15507,
"covered": 3453,
"skipped": 0,
"pct": 22.27
},
"functions": {
"total": 3286,
"covered": 654,
"skipped": 0,
"pct": 19.9
},
"branches": {
"total": 8060,
"covered": 1439,
"skipped": 0,
"pct": 17.85
}
},
"/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana/packages/kbn-ui-framework/src/components/button/button.js": {
"lines": {
"total": 33,
"covered": 28,
"skipped": 0,
"pct": 84.85
},
"functions": {
"total": 7,
"covered": 6,
"skipped": 0,
"pct": 85.71
},
"statements": {
"total": 33,
"covered": 28,
"skipped": 0,
"pct": 84.85
},
"branches": {
"total": 21,
"covered": 16,
"skipped": 0,
"pct": 76.19
}
},
"total": {
"lines": {
"total": 14817,
"covered": 3377,
"skipped": 0,
"pct": 22.79
},
"statements": {
"total": 15507,
"covered": 3453,
"skipped": 0,
"pct": 22.27
},
"functions": {
"total": 3286,
"covered": 654,
"skipped": 0,
"pct": 19.9
},
"branches": {
"total": 8060,
"covered": 1439,
"skipped": 0,
"pct": 17.85
}
},
"/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana/packages/kbn-ui-framework/src/components/button/button_icon/button_icon.js": {
"lines": {
"total": 6,
"covered": 6,
"skipped": 0,
"pct": 100
},
"functions": {
"total": 1,
"covered": 1,
"skipped": 0,
"pct": 100
},
"statements": {
"total": 6,
"covered": 6,
"skipped": 0,
"pct": 100
},
"branches": {
"total": 0,
"covered": 0,
"skipped": 0,
"pct": 100
}
}
}

View file

@ -0,0 +1 @@
2e51fdb932

View file

@ -0,0 +1,23 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import oboe from 'oboe';
import { createReadStream } from 'fs';
export default jsonSummaryPath => oboe(createReadStream(jsonSummaryPath));

View file

@ -0,0 +1,104 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { fromEventPattern, of, fromEvent } from 'rxjs';
import { concatMap, delay, map, takeUntil } from 'rxjs/operators';
import jsonStream from './json_stream';
import { pipe, noop, green, always } from './utils';
import { ingest } from './ingest';
import {
staticSite,
statsAndstaticSiteUrl,
addJsonSummaryPath,
testRunner,
addTimeStamp,
buildId,
coveredFilePath,
ciRunUrl,
itemizeVcs,
} from './transforms';
import { resolve } from 'path';
import { createReadStream } from 'fs';
import readline from 'readline';
const ROOT = '../../../..';
const COVERAGE_INGESTION_KIBANA_ROOT =
process.env.COVERAGE_INGESTION_KIBANA_ROOT || resolve(__dirname, ROOT);
const ms = process.env.DELAY || 0;
const staticSiteUrlBase = process.env.STATIC_SITE_URL_BASE || 'https://kibana-coverage.elastic.dev';
const addPrePopulatedTimeStamp = addTimeStamp(process.env.TIME_STAMP);
const preamble = pipe(statsAndstaticSiteUrl, rootDirAndOrigPath, buildId, addPrePopulatedTimeStamp);
const addTestRunnerAndStaticSiteUrl = pipe(testRunner, staticSite(staticSiteUrlBase));
const transform = jsonSummaryPath => log => vcsInfo => {
const objStream = jsonStream(jsonSummaryPath).on('done', noop);
const itemizeVcsInfo = itemizeVcs(vcsInfo);
const jsonSummary$ = _ => objStream.on('node', '!.*', _);
fromEventPattern(jsonSummary$)
.pipe(
map(preamble),
map(coveredFilePath),
map(itemizeVcsInfo),
map(ciRunUrl),
map(addJsonSummaryPath(jsonSummaryPath)),
map(addTestRunnerAndStaticSiteUrl),
concatMap(x => of(x).pipe(delay(ms)))
)
.subscribe(ingest(log));
};
function rootDirAndOrigPath(obj) {
return {
...obj,
originalFilePath: obj.staticSiteUrl,
COVERAGE_INGESTION_KIBANA_ROOT,
};
}
const mutateVcsInfo = vcsInfo => x => vcsInfo.push(x.trimStart().trimEnd());
const vcsInfoLines$ = vcsInfoFilePath => {
const rl = readline.createInterface({ input: createReadStream(vcsInfoFilePath) });
return fromEvent(rl, 'line').pipe(takeUntil(fromEvent(rl, 'close')));
};
export const prok = ({ jsonSummaryPath, vcsInfoFilePath }, log) => {
validateRoot(COVERAGE_INGESTION_KIBANA_ROOT, log);
logAll(jsonSummaryPath, log);
const xformWithPath = transform(jsonSummaryPath)(log); // On complete
const vcsInfo = [];
vcsInfoLines$(vcsInfoFilePath).subscribe(
mutateVcsInfo(vcsInfo),
err => log.error(err),
always(xformWithPath(vcsInfo))
);
};
function logAll(jsonSummaryPath, log) {
log.debug(`### Code coverage ingestion set to delay for: ${green(ms)} ms`);
log.debug(`### COVERAGE_INGESTION_KIBANA_ROOT: \n\t${green(COVERAGE_INGESTION_KIBANA_ROOT)}`);
log.debug(`### Ingesting from summary json: \n\t[${green(jsonSummaryPath)}]`);
}
function validateRoot(x, log) {
return /kibana$/.test(x) ? noop() : log.warning(`✖✖✖ 'kibana' NOT FOUND in ROOT: ${x}\n`);
}

View file

@ -0,0 +1,87 @@
String path = doc['coveredFilePath.keyword'].value;
if (path.contains('/apm')) return "APM";
else if (path.contains('/canvas')) return "Canvas";
else if (path.contains('/maps')) return "Maps";
else if (path.contains('map_')) return "Maps";
else if (path.contains('/ml')) return "ML";
else if (path.contains('/transform')) return "ML";
else if (path.contains('/infra')) return "Infra";
else if (path.contains('/siem')) return "SIEM";
else if (path.contains('/endpoint')) return "Endpoint";
else if (path.contains('logstash')) return "Logstash";
else if (path.contains('beat')) return "Beats";
else if (path.contains('uptime')) return "Observability";
else if (path.contains('observability')) return "Observability";
else if (path.contains('tutorials')) return "Observability";
else if (path.contains('/code')) return "Code";
else if (path.contains('/cross_cluster_replication')) return "Elasticsearch UI";
else if (path.contains('/index_lifecycle_management')) return "Elasticsearch UI";
else if (path.contains('/index_management')) return "Elasticsearch UI";
else if (path.contains('/license_management')) return "Elasticsearch UI";
else if (path.contains('/management')) return "Elasticsearch UI";
else if (path.contains('/licensing')) return "Elasticsearch UI";
else if (path.contains('/public/management'))return "Elasticsearch UI";
else if (path.contains('/remote_clusters')) return "Elasticsearch UI";
else if (path.contains('/searchprofiler')) return "Elasticsearch UI";
else if (path.contains('/searchprofiler')) return "Elasticsearch UI";
else if (path.contains('/snapshot_restore')) return "Elasticsearch UI";
else if (path.contains('/rollup')) return "Elasticsearch UI";
else if (path.contains('/watcher')) return "Elasticsearch UI";
else if (path.contains('/watcher')) return "Elasticsearch UI";
else if (path.contains('/file_upload')) return "Elasticsearch UI";
else if (path.contains('grokdebugger')) return "Elasticsearch UI";
else if (path.contains('es_ui_shared')) return "Elasticsearch UI";
else if (path.contains('kibana/x-pack/legacy/server/lib')) return "Elasticsearch UI";
else if (path.contains('/public/field_editor')) return "Kibana App";
else if (path.contains('dashboard')) return "Kibana App";
else if (path.contains('discover')) return "Kibana App";
else if (path.contains('graph')) return "Kibana App";
else if (path.contains('timelion')) return "Kibana App";
else if (path.contains('/lens/')) return "Kibana App";
else if (path.contains('/core_plugins')) return "Kibana App";
else if (path.contains('/vislib')) return "Kibana App";
else if (path.contains('/visualize')) return "Kibana App";
else if (path.contains('/public/vis/')) return "Kibana App";
else if (path.contains('/kbn-es')) return "Kibana App";
else if (path.contains('kuery')) return "Kibana App";
else if (path.contains('url_shortening')) return "Kibana App";
else if (path.contains('sample_data')) return "Kibana App";
else if (path.contains('/home')) return "Kibana App";
else if (path.contains('/accessibility')) return "Kibana App";
else if (path.contains('/timeseries')) return "Kibana App";
else if (path.contains('/point_series')) return "Kibana App";
else if (path.contains('security')) return "Kibana Security";
else if (path.contains('privilege')) return "Kibana Security";
else if (path.contains('/spaces')) return "Kibana Security";
else if (path.contains('monitoring')) return "Stack Monitoring";
else if (path.contains('/es_archiver')) return "Kibana Operations";
else if (path.contains('/dev/build')) return "Kibana Operations";
else if (path.contains('/kbn-test')) return "Kibana Operations";
else if (path.contains('upgrade')) return "Kibana Operations";
else if (path.contains('/kbn-dev-utils')) return "Kibana Operations";
else if (path.contains('optimize')) return "Kibana Operations";
else if (path.contains('test_utils')) return "Kibana Operations";
else if (path.contains('kbn-babel-preset')) return "Kibana Operations";
else if (path.contains('kibana/server/routes')) return "Kibana Operations";
else if (path.contains('kibana/src/legacy/server/')) return "Kibana Operations";
else if (path.contains('kibana/scripts/')) return "Kibana Operations";
else if (path.contains('kibana/packages/')) return "Kibana Operations";
else if (path.contains('kibana/src/setup_node_env')) return "Kibana Operations";
else if (path.contains('/kbn-ui-framework')) return "Kibana Design";
else if (path.contains('/ui/ui')) return "Kibana Design";
else if (path.contains('/eui')) return "Kibana Design";
else if (path.contains('/kbn-es-query')) return "Kibana App Arch";
else if (path.contains('/kbn-ui-framework')) return "Kibana App Arch";
else if (path.contains('/kbn-interpreter')) return "Kibana App Arch";
else if (path.contains('courier')) return "Kibana App Arch";
else if (path.contains('kbn-config-schema')) return "Kibana Platform";
else if (path.contains('kibana/src/legacy/utils')) return "Kibana Platform";
else if (path.contains('saved_objects')) return "Kibana Platform";
else if (path.contains('/reporting')) return "Kibana Stack Services";
else if (path.contains('telemetry')) return "Kibana Stack Services";
else if (path.contains('/kbn-i18n')) return "Kibana Stack Services";
else if (path.contains('/kbn-analytics')) return "Kibana Stack Services";
else if (path.contains('/task_manager')) return "Kibana Stack Services";
else if (path.contains('alert')) return "Kibana Stack Services";
else if (path.contains('actions')) return "Kibana Stack Services";
else return "unknown";

View file

@ -0,0 +1,148 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { left, right, fromNullable } from './either';
import { always, id, noop } from './utils';
const maybeTotal = x => (x === 'total' ? left(x) : right(x));
const trimLeftFrom = (text, x) => x.substr(x.indexOf(text));
export const statsAndstaticSiteUrl = (...xs) => {
const [staticSiteUrl] = xs[0][1];
const [stats] = xs[0];
return {
staticSiteUrl,
...stats,
};
};
export const addJsonSummaryPath = jsonSummaryPath => obj => ({
jsonSummaryPath: trimLeftFrom('target', jsonSummaryPath),
...obj,
});
export const truncate = text => obj => {
const { staticSiteUrl } = obj;
if (staticSiteUrl.includes(text)) obj.staticSiteUrl = trimLeftFrom(text, staticSiteUrl);
return obj;
};
export const addTimeStamp = ts => obj => ({
...obj,
'@timestamp': ts,
});
const setTotal = x => obj => (obj.isTotal = x);
const mutateTrue = setTotal(true);
const mutateFalse = setTotal(false);
const root = urlBase => ts => testRunnerType =>
`${urlBase}/${ts}/${testRunnerType.toLowerCase()}-combined`;
const prokForTotalsIndex = mutateTrue => urlRoot => obj =>
right(obj)
.map(mutateTrue)
.map(always(`${urlRoot}/index.html`))
.fold(noop, id);
const prokForCoverageIndex = root => mutateFalse => urlRoot => obj => siteUrl =>
right(siteUrl)
.map(x => {
mutateFalse(obj);
return x;
})
.map(x => x.replace(root, ''))
.map(x => `${urlRoot}${x}.html`)
.fold(noop, id);
export const staticSite = urlBase => obj => {
const { staticSiteUrl, testRunnerType, COVERAGE_INGESTION_KIBANA_ROOT } = obj;
const ts = obj['@timestamp'];
const urlRoot = root(urlBase)(ts)(testRunnerType);
const prokTotal = prokForTotalsIndex(mutateTrue)(urlRoot);
const prokCoverage = prokForCoverageIndex(COVERAGE_INGESTION_KIBANA_ROOT)(mutateFalse)(urlRoot)(
obj
);
const prokForBoth = always(maybeTotal(staticSiteUrl).fold(always(prokTotal(obj)), prokCoverage));
return { ...obj, staticSiteUrl: prokForBoth() };
};
export const coveredFilePath = obj => {
const { staticSiteUrl, COVERAGE_INGESTION_KIBANA_ROOT } = obj;
const withoutCoveredFilePath = always(obj);
const leadingSlashRe = /^\//;
const maybeDropLeadingSlash = x => (leadingSlashRe.test(x) ? right(x) : left(x));
const dropLeadingSlash = x => x.replace(leadingSlashRe, '');
const dropRoot = root => x =>
maybeDropLeadingSlash(x.replace(root, '')).fold(id, dropLeadingSlash);
return maybeTotal(staticSiteUrl)
.map(dropRoot(COVERAGE_INGESTION_KIBANA_ROOT))
.fold(withoutCoveredFilePath, coveredFilePath => ({ ...obj, coveredFilePath }));
};
export const ciRunUrl = obj =>
fromNullable(process.env.CI_RUN_URL).fold(always(obj), ciRunUrl => ({ ...obj, ciRunUrl }));
const size = 50;
const truncateCommitMsg = x => (x.length > size ? `${x.slice(0, 50)}...` : x);
export const itemizeVcs = vcsInfo => obj => {
const [branch, sha, author, commitMsg] = vcsInfo;
return {
...obj,
vcs: {
branch,
sha,
author,
commitMsg: truncateCommitMsg(commitMsg),
vcsUrl: `https://github.com/elastic/kibana/commit/${sha}`,
},
};
};
export const testRunner = obj => {
const { jsonSummaryPath } = obj;
let testRunnerType = 'other';
const upperTestRunnerType = x => {
if (jsonSummaryPath.includes(x)) {
testRunnerType = x.toUpperCase();
return;
}
};
['mocha', 'jest', 'functional'].forEach(upperTestRunnerType);
return {
testRunnerType,
...obj,
};
};
export const buildId = obj => {
const { env } = process;
if (env.BUILD_ID) obj.BUILD_ID = env.BUILD_ID;
return {
...obj,
};
};

View file

@ -0,0 +1,27 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import chalk from 'chalk';
export const pipe = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
export const noop = () => {};
export const green = x => chalk.greenBright.bold(x);
export const id = x => x;
export const always = x => () => x;
export const pretty = x => JSON.stringify(x, null, 2);

View file

@ -0,0 +1,10 @@
#!/bin/bash
EXTRACT_START_DIR=tmp/extracted_coverage
EXTRACT_END_DIR=target/kibana-coverage
COMBINED_EXRACT_DIR=/${EXTRACT_START_DIR}/${EXTRACT_END_DIR}
echo "### Copy mocha reports"
mkdir -p $EXTRACT_END_DIR/mocha-combined
cp -r $COMBINED_EXRACT_DIR/mocha/. $EXTRACT_END_DIR/mocha-combined/

View file

@ -0,0 +1,12 @@
#!/bin/bash
DOWNLOAD_DIR=/tmp/downloaded_coverage
EXTRACT_DIR=/tmp/extracted_coverage
mkdir -p $EXTRACT_DIR
echo "### Extracting downloaded artifacts"
for x in kibana-intake x-pack-intake kibana-oss-tests kibana-xpack-tests; do
tar -xzf $DOWNLOAD_DIR/coverage/${x}/kibana-coverage.tar.gz -C $EXTRACT_DIR || echo "### Error 'tarring': ${x}"
done

View file

@ -0,0 +1,7 @@
#!/bin/bash
DOWNLOAD_DIR=/tmp/downloaded_coverage
EXTRACT_DIR=/tmp/extracted_coverage
echo "### Extracting kibana-xpack-tests"
tar -xzf $DOWNLOAD_DIR/coverage/kibana-xpack-tests/kibana-coverage.tar.gz -C $EXTRACT_DIR

View file

@ -0,0 +1,14 @@
#!/bin/bash
EXTRACT_START_DIR=tmp/extracted_coverage
EXTRACT_END_DIR=target/kibana-coverage
COMBINED_EXRACT_DIR=/${EXTRACT_START_DIR}/${EXTRACT_END_DIR}
PWD=$(pwd)
du -sh $COMBINED_EXRACT_DIR
echo "### Replacing path in json files"
for i in {1..9}; do
sed -i "s|/dev/shm/workspace/kibana|${PWD}|g" $COMBINED_EXRACT_DIR/functional/${i}*.json &
done
wait

View file

@ -0,0 +1,29 @@
#!/bin/bash
echo "### Ingesting Code Coverage"
echo ""
BUILD_ID=$1
export BUILD_ID
CI_RUN_URL=$2
export CI_RUN_URL
echo "### debug CI_RUN_URL: ${CI_RUN_URL}"
ES_HOST="https://${USER_FROM_VAULT}:${PASS_FROM_VAULT}@${HOST_FROM_VAULT}"
export ES_HOST
STATIC_SITE_URL_BASE='https://kibana-coverage.elastic.dev'
export STATIC_SITE_URL_BASE
for x in jest functional mocha; do
echo "### Ingesting coverage for ${x}"
COVERAGE_SUMMARY_FILE=target/kibana-coverage/${x}-combined/coverage-summary.json
node scripts/ingest_coverage.js --verbose --path ${COVERAGE_SUMMARY_FILE} --vcsInfoPath ./VCS_INFO.txt
done
echo "### Ingesting Code Coverage - Complete"
echo ""

View file

@ -0,0 +1,10 @@
#!/bin/bash
EXTRACT_START_DIR=tmp/extracted_coverage
EXTRACT_END_DIR=target/kibana-coverage
COMBINED_EXTRACT_DIR=/${EXTRACT_START_DIR}/${EXTRACT_END_DIR}
echo "### Merge coverage reports"
for x in jest functional; do
yarn nyc report --temp-dir $COMBINED_EXTRACT_DIR/${x} --report-dir $EXTRACT_END_DIR/${x}-combined --reporter=html --reporter=json-summary
done

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Static Code Coverage"
/>
<title>Not Found</title>
</head>
<body>
Requested Resource NOT FOUND
</body>
</html>

View file

@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>KBN - Code Coverage</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<style>
</style>
</head>
<body class="text-center">
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
<header class="masthead mb-auto">
<div class="inner">
<h3 class="masthead-brand">Kibana Code Coverage</h3>
<nav class="nav nav-masthead justify-content-center">
<a class="nav-link active" href="https://kibana-stats.elastic.dev/">Home</a>
<a class="nav-link" href="https://kibana-stats.elastic.dev/app/kibana#/discover/fd694ed0-62f8-11ea-8312-7f2d69b79843?_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-7d%2Cto%3Anow))">Discover</a>

View file

@ -37,6 +37,7 @@ export default {
'<rootDir>/packages',
'<rootDir>/src/test_utils',
'<rootDir>/test/functional/services/remote',
'<rootDir>/src/dev/code_coverage/ingest_coverage',
],
collectCoverageFrom: [
'src/plugins/**/*.{ts,tsx}',

View file

@ -42,6 +42,7 @@ export const IGNORE_FILE_GLOBS = [
'**/{webpackShims,__mocks__}/**/*',
'x-pack/docs/**/*',
'src/core/server/core_app/assets/fonts/**/*',
'src/dev/code_coverage/ingest_coverage/integration_tests/mocks/**/*',
'packages/kbn-utility-types/test-d/**/*',
'**/Jenkinsfile*',
'Dockerfile*',

View file

@ -31,4 +31,4 @@ else
mkdir -p ../kibana/target/kibana-coverage/functional
mv target/kibana-coverage/functional/* ../kibana/target/kibana-coverage/functional/
fi
fi
fi

189
vars/kibanaCoverage.groovy Normal file
View file

@ -0,0 +1,189 @@
def uploadCoverageStaticSite(timestamp) {
def uploadPrefix = "gs://elastic-bekitzur-kibana-coverage-live/"
def uploadPrefixWithTimeStamp = "${uploadPrefix}${timestamp}/"
uploadBaseWebsiteFiles(uploadPrefix)
uploadCoverageHtmls(uploadPrefixWithTimeStamp)
}
def uploadBaseWebsiteFiles(prefix) {
[
'src/dev/code_coverage/www/index.html',
'src/dev/code_coverage/www/404.html'
].each { uploadWithVault(prefix, it) }
}
def uploadCoverageHtmls(prefix) {
[
'target/kibana-coverage/functional-combined',
'target/kibana-coverage/jest-combined',
'target/kibana-coverage/mocha-combined',
].each { uploadWithVault(prefix, it) }
}
def uploadWithVault(prefix, path) {
def vaultSecret = 'secret/gce/elastic-bekitzur/service-account/kibana'
withGcpServiceAccount.fromVaultSecret(vaultSecret, 'value') {
kibanaPipeline.bash("""
gsutil -m cp -r -a public-read -z js,css,html ${path} '${prefix}'
""", "### Upload files to GCS with vault, path: ${path}")
}
}
def prokLinks(title) {
kibanaPipeline.bash('''
cat << EOF > src/dev/code_coverage/www/index_partial_2.html
<a class="nav-link" href="https://kibana-coverage.elastic.dev/${TIME_STAMP}/jest-combined/index.html">Latest Jest</a>
<a class="nav-link" href="https://kibana-coverage.elastic.dev/${TIME_STAMP}/mocha-combined/index.html">Latest Mocha</a>
<a class="nav-link" href="https://kibana-coverage.elastic.dev/${TIME_STAMP}/functional-combined/index.html">Latest FTR</a>
</nav>
</div>
</header>
<main role="main" class="inner cover">
<!-- <h1 class="cover-heading"> - Master Branch</h1>-->
<p class="lead">Use Kibana Stats to mine coverage data</p>
<p class="lead">
<a href="https://kibana-stats.elastic.dev/app/kibana#/dashboard/58b8db70-62f9-11ea-8312-7f2d69b79843?_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-7d%2Cto%3Anow))" class="btn btn-lg btn-primary">Dashboard</a>
</p>
</main>
<footer class="mastfoot mt-auto">
<div class="inner">
<p>Please slack us at <a href="https://app.slack.com/client/T0CUZ52US/C0TR0FAET">#kibana-qa</a> if you've questions</p>
</div>
</footer>
</div>
</body>
</html>
EOF
''', title)
kibanaPipeline.bash('''
cat src/dev/code_coverage/www/index_partial.html > src/dev/code_coverage/www/index.html
cat src/dev/code_coverage/www/index_partial_2.html >> src/dev/code_coverage/www/index.html
echo "### Prok'd Index File: ..."
cat src/dev/code_coverage/www/index.html
''', "### Combine Index Partials")
}
def collectVcsInfo(title) {
kibanaPipeline.bash('''
predicate() {
x=$1
if [ -n "$x" ]; then
return
else
echo "### 1 or more variables that Code Coverage needs, are undefined"
exit 1
fi
}
CMD="git log --pretty=format"
XS=("${GIT_BRANCH}" \
"$(${CMD}":%h" -1)" \
"$(${CMD}":%an" -1)" \
"$(${CMD}":%s" -1)")
touch VCS_INFO.txt
for X in "${!XS[@]}"; do
{
predicate "${XS[X]}"
echo "${XS[X]}" >> VCS_INFO.txt
}
done
echo "### VCS_INFO:"
cat VCS_INFO.txt
''', title
)
}
def bootMergeAndIngest(buildNum, buildUrl, title) {
kibanaPipeline.bash("""
source src/dev/ci_setup/setup_env.sh
# bootstrap from x-pack folder
cd x-pack
yarn kbn bootstrap --prefer-offline
# Return to project root
cd ..
. src/dev/code_coverage/shell_scripts/extract_archives.sh
. src/dev/code_coverage/shell_scripts/fix_html_reports_parallel.sh
. src/dev/code_coverage/shell_scripts/merge_jest_and_functional.sh
. src/dev/code_coverage/shell_scripts/copy_mocha_reports.sh
. src/dev/code_coverage/shell_scripts/ingest_coverage.sh ${buildNum} ${buildUrl}
""", title)
}
def ingestWithVault(buildNum, buildUrl, title) {
def vaultSecret = 'secret/kibana-issues/prod/coverage/elasticsearch'
withVaultSecret(secret: vaultSecret, secret_field: 'host', variable_name: 'HOST_FROM_VAULT') {
withVaultSecret(secret: vaultSecret, secret_field: 'username', variable_name: 'USER_FROM_VAULT') {
withVaultSecret(secret: vaultSecret, secret_field: 'password', variable_name: 'PASS_FROM_VAULT') {
bootMergeAndIngest(buildNum, buildUrl, title)
}
}
}
}
def ingest(timestamp, title) {
withEnv([
"TIME_STAMP=${timestamp}",
]) {
ingestWithVault(BUILD_NUMBER, BUILD_URL, title)
}
}
def runTests() {
parallel([
'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'),
'x-pack-intake-agent': {
withEnv([
'NODE_ENV=test' // Needed for jest tests only
]) {
workers.intake('x-pack-intake', './test/scripts/jenkins_xpack.sh')()
}
},
'kibana-oss-agent' : workers.functional(
'kibana-oss-tests',
{ kibanaPipeline.buildOss() },
ossProks()
),
'kibana-xpack-agent' : workers.functional(
'kibana-xpack-tests',
{ kibanaPipeline.buildXpack() },
xpackProks()
),
])
}
def ossProks() {
return [
'oss-ciGroup1' : kibanaPipeline.ossCiGroupProcess(1),
'oss-ciGroup2' : kibanaPipeline.ossCiGroupProcess(2),
'oss-ciGroup3' : kibanaPipeline.ossCiGroupProcess(3),
'oss-ciGroup4' : kibanaPipeline.ossCiGroupProcess(4),
'oss-ciGroup5' : kibanaPipeline.ossCiGroupProcess(5),
'oss-ciGroup6' : kibanaPipeline.ossCiGroupProcess(6),
'oss-ciGroup7' : kibanaPipeline.ossCiGroupProcess(7),
'oss-ciGroup8' : kibanaPipeline.ossCiGroupProcess(8),
'oss-ciGroup9' : kibanaPipeline.ossCiGroupProcess(9),
'oss-ciGroup10': kibanaPipeline.ossCiGroupProcess(10),
'oss-ciGroup11': kibanaPipeline.ossCiGroupProcess(11),
'oss-ciGroup12': kibanaPipeline.ossCiGroupProcess(12),
]
}
def xpackProks() {
return [
'xpack-ciGroup1' : kibanaPipeline.xpackCiGroupProcess(1),
'xpack-ciGroup2' : kibanaPipeline.xpackCiGroupProcess(2),
'xpack-ciGroup3' : kibanaPipeline.xpackCiGroupProcess(3),
'xpack-ciGroup4' : kibanaPipeline.xpackCiGroupProcess(4),
'xpack-ciGroup5' : kibanaPipeline.xpackCiGroupProcess(5),
'xpack-ciGroup6' : kibanaPipeline.xpackCiGroupProcess(6),
'xpack-ciGroup7' : kibanaPipeline.xpackCiGroupProcess(7),
'xpack-ciGroup8' : kibanaPipeline.xpackCiGroupProcess(8),
'xpack-ciGroup9' : kibanaPipeline.xpackCiGroupProcess(9),
'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10),
]
}
return this