diff --git a/Jenkinsfile b/Jenkinsfile index d43da6e0bee04..79d3c93006cb6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -40,7 +40,12 @@ kibanaPipeline(timeoutMinutes: 135, checkPrChanges: true) { 'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9), 'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10), 'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'), - 'xpack-siemCypress': kibanaPipeline.functionalTestProcess('xpack-siemCypress', './test/scripts/jenkins_siem_cypress.sh'), + 'xpack-siemCypress': { processNumber -> + whenChanged(['x-pack/legacy/plugins/siem/', 'x-pack/test/siem_cypress/']) { + kibanaPipeline.functionalTestProcess('xpack-siemCypress', './test/scripts/jenkins_siem_cypress.sh')(processNumber) + } + }, + // 'xpack-visualRegression': kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh'), ]), ]) diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy index 0176424452d07..965fb1d4e108e 100644 --- a/vars/githubPr.groovy +++ b/vars/githubPr.groovy @@ -169,7 +169,20 @@ def getNextCommentMessage(previousCommentInfo = [:]) { ## :broken_heart: Build Failed * [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL}) * Commit: ${getCommitHash()} + * [Pipeline Steps](${env.BUILD_URL}flowGraphTable) (look for red circles / failed steps) + * [Interpreting CI Failures](https://www.elastic.co/guide/en/kibana/current/interpreting-ci-failures.html) """ + + try { + def steps = getFailedSteps() + if (steps?.size() > 0) { + def list = steps.collect { "* [${it.displayName}](${it.logs})" }.join("\n") + messages << "### Failed CI Steps\n${list}" + } + } catch (ex) { + buildUtils.printStacktrace(ex) + print "Error retrieving failed pipeline steps for PR comment, will skip this section" + } } messages << getTestFailuresMessage() @@ -220,3 +233,9 @@ def deleteComment(commentId) { def getCommitHash() { return env.ghprbActualCommit } + +def getFailedSteps() { + return jenkinsApi.getFailedSteps()?.findAll { step -> + step.displayName != 'Check out from version control' + } +} diff --git a/vars/jenkinsApi.groovy b/vars/jenkinsApi.groovy new file mode 100644 index 0000000000000..1ea4c3dd76b8d --- /dev/null +++ b/vars/jenkinsApi.groovy @@ -0,0 +1,21 @@ +def getSteps() { + def url = "${env.BUILD_URL}api/json?tree=actions[nodes[iconColor,running,displayName,id,parents]]" + def responseRaw = httpRequest([ method: "GET", url: url ]) + def response = toJSON(responseRaw) + + def graphAction = response?.actions?.find { it._class == "org.jenkinsci.plugins.workflow.job.views.FlowGraphAction" } + + return graphAction?.nodes +} + +def getFailedSteps() { + def steps = getSteps() + def failedSteps = steps?.findAll { it.iconColor == "red" && it._class == "org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode" } + failedSteps.each { step -> + step.logs = "${env.BUILD_URL}execution/node/${step.id}/log".toString() + } + + return failedSteps +} + +return this diff --git a/vars/prChanges.groovy b/vars/prChanges.groovy index a9eb9027a0597..d7f46ee7be23e 100644 --- a/vars/prChanges.groovy +++ b/vars/prChanges.groovy @@ -1,3 +1,6 @@ +import groovy.transform.Field + +public static @Field PR_CHANGES_CACHE = null def getSkippablePaths() { return [ @@ -36,9 +39,13 @@ def areChangesSkippable() { } def getChanges() { - withGithubCredentials { - return githubPrs.getChanges(env.ghprbPullId) + if (!PR_CHANGES_CACHE && env.ghprbPullId) { + withGithubCredentials { + PR_CHANGES_CACHE = githubPrs.getChanges(env.ghprbPullId) + } } + + return PR_CHANGES_CACHE } def getChangedFiles() { diff --git a/vars/whenChanged.groovy b/vars/whenChanged.groovy new file mode 100644 index 0000000000000..c58ec83f2b051 --- /dev/null +++ b/vars/whenChanged.groovy @@ -0,0 +1,57 @@ +/* + whenChanged('some/path') { yourCode() } can be used to execute pipeline code in PRs only when changes are detected on paths that you specify. + The specified code blocks will also always be executed during the non-PR jobs for tracked branches. + + You have the option of passing in path prefixes, or regexes. Single or multiple. + Path specifications are NOT globby, they are only prefixes. + Specifying multiple will treat them as ORs. + + Example Usages: + whenChanged('a/path/prefix/') { someCode() } + whenChanged(startsWith: 'a/path/prefix/') { someCode() } // Same as above + whenChanged(['prefix1/', 'prefix2/']) { someCode() } + whenChanged(regex: /\.test\.js$/) { someCode() } + whenChanged(regex: [/abc/, /xyz/]) { someCode() } +*/ + +def call(String startsWithString, Closure closure) { + return whenChanged([ startsWith: startsWithString ], closure) +} + +def call(List startsWithStrings, Closure closure) { + return whenChanged([ startsWith: startsWithStrings ], closure) +} + +def call(Map params, Closure closure) { + if (!githubPr.isPr()) { + return closure() + } + + def files = prChanges.getChangedFiles() + def hasMatch = false + + if (params.regex) { + params.regex = [] + params.regex + print "Checking PR for changes that match: ${params.regex.join(', ')}" + hasMatch = !!files.find { file -> + params.regex.find { regex -> file =~ regex } + } + } + + if (!hasMatch && params.startsWith) { + params.startsWith = [] + params.startsWith + print "Checking PR for changes that start with: ${params.startsWith.join(', ')}" + hasMatch = !!files.find { file -> + params.startsWith.find { str -> file.startsWith(str) } + } + } + + if (hasMatch) { + print "Changes found, executing pipeline." + closure() + } else { + print "No changes found, skipping." + } +} + +return this diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts index 7f1919fbea684..f30629789b4ed 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts +++ b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; import { i18n } from '@kbn/i18n'; import { AlertType } from '../../common'; import { AlertNavigationHandler } from './types'; @@ -36,7 +35,7 @@ export class AlertNavigationRegistry { public registerDefault(consumer: string, handler: AlertNavigationHandler) { if (this.hasDefaultHandler(consumer)) { - throw Boom.badRequest( + throw new Error( i18n.translate('xpack.alerting.alertNavigationRegistry.register.duplicateDefaultError', { defaultMessage: 'Default Navigation within "{consumer}" is already registered.', values: { @@ -54,7 +53,7 @@ export class AlertNavigationRegistry { public register(consumer: string, alertType: AlertType, handler: AlertNavigationHandler) { if (this.hasTypedHandler(consumer, alertType)) { - throw Boom.badRequest( + throw new Error( i18n.translate('xpack.alerting.alertNavigationRegistry.register.duplicateNavigationError', { defaultMessage: 'Navigation for Alert type "{alertType}" within "{consumer}" is already registered.', @@ -78,7 +77,7 @@ export class AlertNavigationRegistry { return (consumerHandlers.get(alertType.id) ?? consumerHandlers.get(DEFAULT_HANDLER))!; } - throw Boom.badRequest( + throw new Error( i18n.translate('xpack.alerting.alertNavigationRegistry.get.missingNavigationError', { defaultMessage: 'Navigation for Alert type "{alertType}" within "{consumer}" is not registered.', diff --git a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts index 5be6ed8828e6f..10b3dbbd9b452 100644 --- a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts +++ b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.test.ts @@ -13,11 +13,12 @@ describe('cluster_serialization', () => { expect(() => deserializeCluster('foo', 'bar')).toThrowError(); }); - it('should deserialize a complete cluster object', () => { + it('should deserialize a complete default cluster object', () => { expect( deserializeCluster('test_cluster', { seeds: ['localhost:9300'], connected: true, + mode: 'sniff', num_nodes_connected: 1, max_connections_per_cluster: 3, initial_connect_timeout: '30s', @@ -29,6 +30,7 @@ describe('cluster_serialization', () => { }) ).toEqual({ name: 'test_cluster', + mode: 'sniff', seeds: ['localhost:9300'], isConnected: true, connectedNodesCount: 1, @@ -40,6 +42,37 @@ describe('cluster_serialization', () => { }); }); + it('should deserialize a complete "proxy" mode cluster object', () => { + expect( + deserializeCluster('test_cluster', { + proxy_address: 'localhost:9300', + mode: 'proxy', + connected: true, + num_proxy_sockets_connected: 1, + max_proxy_socket_connections: 3, + initial_connect_timeout: '30s', + skip_unavailable: false, + server_name: 'my_server_name', + transport: { + ping_schedule: '-1', + compress: false, + }, + }) + ).toEqual({ + name: 'test_cluster', + mode: 'proxy', + proxyAddress: 'localhost:9300', + isConnected: true, + connectedSocketsCount: 1, + proxySocketConnections: 3, + initialConnectTimeout: '30s', + skipUnavailable: false, + transportPingSchedule: '-1', + transportCompress: false, + serverName: 'my_server_name', + }); + }); + it('should deserialize a cluster object without transport information', () => { expect( deserializeCluster('test_cluster', { diff --git a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts index 53dc72eb1695a..fbea311cdeefa 100644 --- a/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts +++ b/x-pack/plugins/remote_clusters/common/lib/cluster_serialization.ts @@ -18,9 +18,10 @@ export interface ClusterEs { ping_schedule?: string; compress?: boolean; }; - address?: string; - max_socket_connections?: number; - num_sockets_connected?: number; + proxy_address?: string; + max_proxy_socket_connections?: number; + num_proxy_sockets_connected?: number; + server_name?: string; } export interface Cluster { @@ -77,9 +78,10 @@ export function deserializeCluster( initial_connect_timeout: initialConnectTimeout, skip_unavailable: skipUnavailable, transport, - address: proxyAddress, - max_socket_connections: proxySocketConnections, - num_sockets_connected: connectedSocketsCount, + proxy_address: proxyAddress, + max_proxy_socket_connections: proxySocketConnections, + num_proxy_sockets_connected: connectedSocketsCount, + server_name: serverName, } = esClusterObject; let deserializedClusterObject: Cluster = { @@ -94,6 +96,7 @@ export function deserializeCluster( proxyAddress, proxySocketConnections, connectedSocketsCount, + serverName, }; if (transport) { diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts index abd44977d8e46..8938f342674f0 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts @@ -45,16 +45,9 @@ export const register = (deps: RouteDependencies): void => { ? get(clusterSettings, `persistent.cluster.remote[${clusterName}].proxy`, undefined) : undefined; - // server_name is not available via the GET /_remote/info API, so we get it from the cluster settings - // Per https://github.com/elastic/kibana/pull/26067#issuecomment-441848124, we only look at persistent settings - const serverName = isPersistent - ? get(clusterSettings, `persistent.cluster.remote[${clusterName}].server_name`, undefined) - : undefined; - return { ...deserializeCluster(clusterName, cluster, deprecatedProxyAddress), isConfiguredByNode, - serverName, }; }); diff --git a/x-pack/test/accessibility/apps/login_page.ts b/x-pack/test/accessibility/apps/login_page.ts index 5b18b6be9e3a4..8c673bb332d91 100644 --- a/x-pack/test/accessibility/apps/login_page.ts +++ b/x-pack/test/accessibility/apps/login_page.ts @@ -28,14 +28,33 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.security.forceLogout(); }); - it('meets a11y requirements', async () => { + it('login page meets a11y requirements', async () => { await PageObjects.common.navigateToApp('login'); await retry.waitFor( 'login page visible', async () => await testSubjects.exists('loginSubmit') ); + await a11y.testAppSnapshot(); + }); + + it('User can login with a11y requirements', async () => { + await PageObjects.security.login(); + await a11y.testAppSnapshot(); + }); + + it('Wrong credentials message meets a11y requirements', async () => { + await PageObjects.security.loginPage.login('wrong-user', 'wrong-password', { + expectSuccess: false, + }); + await PageObjects.security.loginPage.getErrorMessage(); + await a11y.testAppSnapshot(); + }); + it('Logout message acknowledges a11y requirements', async () => { + await PageObjects.security.login(); + await PageObjects.security.logout(); + await testSubjects.getVisibleText('loginInfoMessage'); await a11y.testAppSnapshot(); }); }); diff --git a/x-pack/test/functional/apps/uptime/settings.ts b/x-pack/test/functional/apps/uptime/settings.ts index 0e804dd161c6b..aafb145a1b9b0 100644 --- a/x-pack/test/functional/apps/uptime/settings.ts +++ b/x-pack/test/functional/apps/uptime/settings.ts @@ -16,7 +16,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['uptime']); const es = getService('es'); - describe('uptime settings page', () => { + // Flaky https://github.com/elastic/kibana/issues/60866 + describe.skip('uptime settings page', () => { const settingsPage = () => pageObjects.uptime.settings; beforeEach('navigate to clean app root', async () => { // make 10 checks diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts index 6c41c2cab801e..2a50c0117eae9 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts @@ -111,11 +111,11 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) const table = await find.byCssSelector('[data-test-subj="alertsList"] table'); const $ = await table.parseDomContent(); const rows = $.findTestSubjects('alert-row').toArray(); - expect(rows.length).not.to.eql(0); + expect(rows.length).to.eql(0); const emptyRow = await find.byCssSelector( '[data-test-subj="alertsList"] table .euiTableRow' ); - expect(await emptyRow.getVisibleText()).not.to.eql('No items found'); + expect(await emptyRow.getVisibleText()).to.eql('No items found'); }); return true; },