diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-22 21:09:27 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-22 21:09:27 +0300 |
commit | 3ce55b46dfae23d14818ed2630891be8aa3e83e0 (patch) | |
tree | be2b93cf35b854871d8cbb2c22a8d5c11d6caf03 | |
parent | d1cb802bac5dc182342adb9b8f71dbf466cfa501 (diff) |
Add latest changes from gitlab-org/gitlab@master
72 files changed, 812 insertions, 208 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 3a71972f7ab..422efc3b0b3 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -76,9 +76,9 @@ Dangerfile @gl-quality/eng-prod /doc/user/project/code_owners.md @reprazent @kerrizor @garyh [Telemetry] -/ee/lib/gitlab/usage_data_counters/ @gitlab-org/growth/telemetry -/ee/lib/ee/gitlab/usage_data.rb @gitlab-org/growth/telemetry -/lib/gitlab/grafana_embed_usage_data.rb @gitlab-org/growth/telemetry -/lib/gitlab/usage_data.rb @gitlab-org/growth/telemetry -/lib/gitlab/cycle_analytics/usage_data.rb @gitlab-org/growth/telemetry -/lib/gitlab/usage_data_counters/ @gitlab-org/growth/telemetry +/ee/lib/gitlab/usage_data_counters/ @gitlab-org/growth/telemetry/engineers +/ee/lib/ee/gitlab/usage_data.rb @gitlab-org/growth/telemetry/engineers +/lib/gitlab/grafana_embed_usage_data.rb @gitlab-org/growth/telemetry/engineers +/lib/gitlab/usage_data.rb @gitlab-org/growth/telemetry/engineers +/lib/gitlab/cycle_analytics/usage_data.rb @gitlab-org/growth/telemetry/engineers +/lib/gitlab/usage_data_counters/ @gitlab-org/growth/telemetry/engineers diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1ce89720cda..279ef50bc32 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -153,23 +153,6 @@ Layout/SpaceInsideBlockBraces: Layout/SpaceInsideParens: Enabled: false -# Offense count: 19 -Lint/DuplicateMethods: - Exclude: - - 'app/models/commit.rb' - - 'app/models/note.rb' - - 'lib/bitbucket/representation/repo.rb' - - 'lib/declarative_policy/base.rb' - - 'lib/gitlab/auth/ldap/person.rb' - - 'lib/gitlab/auth/o_auth/user.rb' - - 'lib/gitlab/ci/build/artifacts/metadata/entry.rb' - - 'lib/gitlab/cycle_analytics/base_event_fetcher.rb' - - 'lib/gitlab/diff/formatters/base_formatter.rb' - - 'lib/gitlab/git/blob.rb' - - 'lib/gitlab/git/repository.rb' - - 'lib/gitlab/git/tree.rb' - - 'lib/gitlab/git/wiki_page.rb' - # Offense count: 157 # Configuration parameters: MaximumRangeSize. Lint/MissingCopEnableDirective: diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index ab534f0d0aa..b7330770e96 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -d758ff48fbf8392e08626b60685d037373347d72 +e3bedb3507c01fbe8395dd76589e095d7da14e66 @@ -328,7 +328,7 @@ group :metrics do gem 'method_source', '~> 0.8', require: false # Prometheus - gem 'prometheus-client-mmap', '~> 0.10.0' + gem 'prometheus-client-mmap', '~> 0.11.0' gem 'raindrops', '~> 0.18' end diff --git a/Gemfile.lock b/Gemfile.lock index 2efa46e5bca..a15b4ea3bdb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -787,7 +787,7 @@ GEM parser unparser procto (0.0.3) - prometheus-client-mmap (0.10.0) + prometheus-client-mmap (0.11.0) pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) @@ -1345,7 +1345,7 @@ DEPENDENCIES pg (~> 1.1) png_quantizator (~> 0.2.1) premailer-rails (~> 1.10.3) - prometheus-client-mmap (~> 0.10.0) + prometheus-client-mmap (~> 0.11.0) pry-byebug (~> 3.5.1) pry-rails (~> 0.3.9) rack (~> 2.0.9) diff --git a/app/assets/javascripts/helpers/monitor_helper.js b/app/assets/javascripts/helpers/monitor_helper.js index 5f85ee58779..5e345321013 100644 --- a/app/assets/javascripts/helpers/monitor_helper.js +++ b/app/assets/javascripts/helpers/monitor_helper.js @@ -49,7 +49,7 @@ const multiMetricLabel = metricAttributes => { * @param {Object} metricAttributes - Default metric attribute values (e.g. method, instance) * @returns {String} The formatted query label */ -const getSeriesLabel = (queryLabel, metricAttributes) => { +export const getSeriesLabel = (queryLabel, metricAttributes) => { return ( singleAttributeLabel(queryLabel, metricAttributes) || templatedLabel(queryLabel, metricAttributes) || @@ -63,7 +63,6 @@ const getSeriesLabel = (queryLabel, metricAttributes) => { * @param {Object} defaultConfig - Default chart config values (e.g. lineStyle, name) * @returns {Array} The formatted values */ -// eslint-disable-next-line import/prefer-default-export export const makeDataSeries = (queryResults, defaultConfig) => queryResults.map(result => { return { diff --git a/app/assets/javascripts/jira_import/queries/get_jira_user_mapping.mutation.graphql b/app/assets/javascripts/jira_import/queries/get_jira_user_mapping.mutation.graphql index 1f7c52eec58..cca33af342c 100644 --- a/app/assets/javascripts/jira_import/queries/get_jira_user_mapping.mutation.graphql +++ b/app/assets/javascripts/jira_import/queries/get_jira_user_mapping.mutation.graphql @@ -5,6 +5,8 @@ mutation($input: JiraImportUsersInput!) { jiraDisplayName jiraEmail gitlabId + gitlabName + gitlabUsername } errors } diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue index 3e3c8408de3..610bef37fdb 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue @@ -30,6 +30,7 @@ import MonitorStackedColumnChart from './charts/stacked_column.vue'; import TrackEventDirective from '~/vue_shared/directives/track_event'; import AlertWidget from './alert_widget.vue'; import { timeRangeToUrl, downloadCSVOptions, generateLinkToChartOptions } from '../utils'; +import { graphDataToCsv } from '../csv_export'; const events = { timeRangeZoom: 'timerangezoom', @@ -148,13 +149,10 @@ export default { return null; }, csvText() { - const chartData = this.graphData?.metrics[0].result[0].values || []; - const yLabel = this.graphData.y_label; - const header = `timestamp,${yLabel}\r\n`; // eslint-disable-line @gitlab/require-i18n-strings - return chartData.reduce((csv, data) => { - const row = data.join(','); - return `${csv}${row}\r\n`; - }, header); + if (this.graphData) { + return graphDataToCsv(this.graphData); + } + return null; }, downloadCsv() { const data = new Blob([this.csvText], { type: 'text/plain' }); diff --git a/app/assets/javascripts/monitoring/csv_export.js b/app/assets/javascripts/monitoring/csv_export.js new file mode 100644 index 00000000000..734e8dc07a7 --- /dev/null +++ b/app/assets/javascripts/monitoring/csv_export.js @@ -0,0 +1,147 @@ +import { getSeriesLabel } from '~/helpers/monitor_helper'; + +/** + * Returns a label for a header of the csv. + * + * Includes double quotes ("") in case the header includes commas or other separator. + * + * @param {String} axisLabel + * @param {String} metricLabel + * @param {Object} metricAttributes + */ +const csvHeader = (axisLabel, metricLabel, metricAttributes = {}) => + `${axisLabel} > ${getSeriesLabel(metricLabel, metricAttributes)}`; + +/** + * Returns an array with the header labels given a list of metrics + * + * ``` + * metrics = [ + * { + * label: "..." // user-defined label + * result: [ + * { + * metric: { ... } // metricAttributes + * }, + * ... + * ] + * }, + * ... + * ] + * ``` + * + * When metrics have a `label` or `metricAttributes`, they are + * used to generate the column name. + * + * @param {String} axisLabel - Main label + * @param {Array} metrics - Metrics with results + */ +const csvMetricHeaders = (axisLabel, metrics) => + metrics.flatMap(({ label, result }) => + // The `metric` in a `result` is a map of `metricAttributes` + // contains key-values to identify the series, rename it + // here for clarity. + result.map(({ metric: metricAttributes }) => { + return csvHeader(axisLabel, label, metricAttributes); + }), + ); + +/** + * Returns a (flat) array with all the values arrays in each + * metric and series + * + * ``` + * metrics = [ + * { + * result: [ + * { + * values: [ ... ] // `values` + * }, + * ... + * ] + * }, + * ... + * ] + * ``` + * + * @param {Array} metrics - Metrics with results + */ +const csvMetricValues = metrics => + metrics.flatMap(({ result }) => result.map(res => res.values || [])); + +/** + * Returns headers and rows for csv, sorted by their timestamp. + * + * { + * headers: ["timestamp", "<col_1_name>", "col_2_name"], + * rows: [ + * [ <timestamp>, <col_1_value>, <col_2_value> ], + * [ <timestamp>, <col_1_value>, <col_2_value> ] + * ... + * ] + * } + * + * @param {Array} metricHeaders + * @param {Array} metricValues + */ +const csvData = (metricHeaders, metricValues) => { + const rowsByTimestamp = {}; + + metricValues.forEach((values, colIndex) => { + values.forEach(([timestamp, value]) => { + if (!rowsByTimestamp[timestamp]) { + rowsByTimestamp[timestamp] = []; + } + // `value` should be in the right column + rowsByTimestamp[timestamp][colIndex] = value; + }); + }); + + const rows = Object.keys(rowsByTimestamp) + .sort() + .map(timestamp => { + // force each row to have the same number of entries + rowsByTimestamp[timestamp].length = metricHeaders.length; + // add timestamp as the first entry + return [timestamp, ...rowsByTimestamp[timestamp]]; + }); + + // Escape double quotes and enclose headers: + // "If double-quotes are used to enclose fields, then a double-quote + // appearing inside a field must be escaped by preceding it with + // another double quote." + // https://tools.ietf.org/html/rfc4180#page-2 + const headers = metricHeaders.map(header => `"${header.replace(/"/g, '""')}"`); + + return { + headers: ['timestamp', ...headers], + rows, + }; +}; + +/** + * Returns dashboard panel's data in a string in CSV format + * + * @param {Object} graphData - Panel contents + * @returns {String} + */ +// eslint-disable-next-line import/prefer-default-export +export const graphDataToCsv = graphData => { + const delimiter = ','; + const br = '\r\n'; + const { metrics = [], y_label: axisLabel } = graphData; + + const metricsWithResults = metrics.filter(metric => metric.result); + const metricHeaders = csvMetricHeaders(axisLabel, metricsWithResults); + const metricValues = csvMetricValues(metricsWithResults); + const { headers, rows } = csvData(metricHeaders, metricValues); + + if (rows.length === 0) { + return ''; + } + + const headerLine = headers.join(delimiter) + br; + const lines = rows.map(row => row.join(delimiter)); + + return headerLine + lines.join(br) + br; +}; diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index a441882a47d..05cbdcf8797 100644 --- a/app/assets/javascripts/monitoring/stores/actions.js +++ b/app/assets/javascripts/monitoring/stores/actions.js @@ -506,6 +506,3 @@ export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQuery return Promise.all(optionsRequests); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/monitoring/stores/getters.js b/app/assets/javascripts/monitoring/stores/getters.js index 3aa711a0509..8ed83cf02fe 100644 --- a/app/assets/javascripts/monitoring/stores/getters.js +++ b/app/assets/javascripts/monitoring/stores/getters.js @@ -170,6 +170,3 @@ export const getCustomVariablesParams = state => */ export const fullDashboardPath = state => normalizeCustomDashboardPath(state.currentDashboard, state.customDashboardBasePath); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/packages/details/components/app.vue b/app/assets/javascripts/packages/details/components/app.vue index da4429f5134..25213f94368 100644 --- a/app/assets/javascripts/packages/details/components/app.vue +++ b/app/assets/javascripts/packages/details/components/app.vue @@ -287,7 +287,7 @@ export default { </gl-tab> <gl-tab - :title="__('Versions')" + :title="__('Other versions')" title-item-class="js-versions-tab" @click="getPackageVersions" > diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue index 8746784aa57..257480185c7 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue @@ -29,7 +29,7 @@ export default { }, methods: { ...mapActions([ - 'fetchFullReport', + 'fetchTestSuite', 'fetchSummary', 'setSelectedSuiteIndex', 'removeSelectedSuiteIndex', @@ -40,10 +40,8 @@ export default { summaryTableRowClick(index) { this.setSelectedSuiteIndex(index); - // Fetch the full report when the user clicks to see more details - if (!this.hasFullReport) { - this.fetchFullReport(); - } + // Fetch the test suite when the user clicks to see more details + this.fetchTestSuite(index); }, beforeEnterTransition() { document.documentElement.style.overflowX = 'hidden'; diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index f1102a9bddf..e658352f20f 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -122,11 +122,12 @@ const createTestDetails = () => { } const el = document.querySelector('#js-pipeline-tests-detail'); - const { fullReportEndpoint, summaryEndpoint, countEndpoint } = el?.dataset || {}; + const { fullReportEndpoint, summaryEndpoint, suiteEndpoint, countEndpoint } = el?.dataset || {}; const testReportsStore = createTestReportsStore({ fullReportEndpoint, summaryEndpoint: summaryEndpoint || countEndpoint, + suiteEndpoint, useBuildSummaryReport: window.gon?.features?.buildReportSummary, }); diff --git a/app/assets/javascripts/pipelines/stores/test_reports/actions.js b/app/assets/javascripts/pipelines/stores/test_reports/actions.js index cb79a1a6ca5..f39222fe4df 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/actions.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/actions.js @@ -33,6 +33,31 @@ export const fetchSummary = ({ state, commit, dispatch }) => { }); }; +export const fetchTestSuite = ({ state, commit, dispatch }, index) => { + const { hasFullSuite } = state.testReports?.test_suites?.[index] || {}; + // We don't need to fetch the suite if we have the information already + if (state.hasFullReport || hasFullSuite) { + return Promise.resolve(); + } + + dispatch('toggleLoading'); + + const { name = '', build_ids = [] } = state.testReports?.test_suites?.[index] || {}; + // Replacing `/:suite_name.json` with the name of the suite. Including the extra characters + // to ensure that we replace exactly the template part of the URL string + const endpoint = state.suiteEndpoint?.replace('/:suite_name.json', `/${name}.json`); + + return axios + .get(endpoint, { params: { build_ids } }) + .then(({ data }) => commit(types.SET_SUITE, { suite: data, index })) + .catch(() => { + createFlash(s__('TestReports|There was an error fetching the test suite.')); + }) + .finally(() => { + dispatch('toggleLoading'); + }); +}; + export const fetchFullReport = ({ state, commit, dispatch }) => { dispatch('toggleLoading'); diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js index 76405557b51..97a638616fa 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js @@ -1,4 +1,5 @@ export const SET_REPORTS = 'SET_REPORTS'; export const SET_SELECTED_SUITE_INDEX = 'SET_SELECTED_SUITE_INDEX'; export const SET_SUMMARY = 'SET_SUMMARY'; +export const SET_SUITE = 'SET_SUITE'; export const TOGGLE_LOADING = 'TOGGLE_LOADING'; diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js index 2531ab1e87c..84056c022ac 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js @@ -5,6 +5,10 @@ export default { Object.assign(state, { testReports, hasFullReport: true }); }, + [types.SET_SUITE](state, { suite = {}, index = null }) { + state.testReports.test_suites[index] = { ...suite, hasFullSuite: true }; + }, + [types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) { Object.assign(state, { selectedSuiteIndex }); }, diff --git a/app/assets/javascripts/pipelines/stores/test_reports/state.js b/app/assets/javascripts/pipelines/stores/test_reports/state.js index bcf5c147916..dd6adfa8940 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/state.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/state.js @@ -1,9 +1,11 @@ export default ({ fullReportEndpoint = '', summaryEndpoint = '', + suiteEndpoint = '', useBuildSummaryReport = false, }) => ({ summaryEndpoint, + suiteEndpoint, fullReportEndpoint, testReports: {}, selectedSuiteIndex: null, diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb index 13fc2514f0c..6d46009fa4d 100644 --- a/app/models/audit_event.rb +++ b/app/models/audit_event.rb @@ -5,7 +5,7 @@ class AuditEvent < ApplicationRecord include IgnorableColumns include BulkInsertSafe - PARALLEL_PERSISTENCE_COLUMNS = [:author_name, :entity_path].freeze + PARALLEL_PERSISTENCE_COLUMNS = [:author_name, :entity_path, :target_details].freeze ignore_column :updated_at, remove_with: '13.4', remove_after: '2020-09-22' diff --git a/app/models/commit.rb b/app/models/commit.rb index 53bcdf8165f..4f18ece9e50 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -21,7 +21,6 @@ class Commit participant :committer participant :notes_with_associations - attr_accessor :author attr_accessor :redacted_description_html attr_accessor :redacted_title_html attr_accessor :redacted_full_title_html diff --git a/app/models/note.rb b/app/models/note.rb index 2db7e4e406d..ae5cb82f093 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -61,7 +61,7 @@ class Note < ApplicationRecord attr_accessor :commands_changes # A special role that may be displayed on issuable's discussions - attr_accessor :special_role + attr_reader :special_role default_value_for :system, false diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index a2d6b2e18a9..2f3d0660caa 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -1,5 +1,5 @@ - page_title _("Blame"), @blob.path, @ref -- link_icon = icon("link") +- link_icon = sprite_icon("link", size: 12) #blob-content-holder.tree-holder = render "projects/blob/breadcrumb", blob: @blob, blame: true diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml index be947b42e25..ee849fd34c7 100644 --- a/app/views/projects/pipelines/_with_tabs.html.haml +++ b/app/views/projects/pipelines/_with_tabs.html.haml @@ -88,5 +88,6 @@ #js-tab-tests.tab-pane #js-pipeline-tests-detail{ data: { full_report_endpoint: test_report_project_pipeline_path(@project, @pipeline, format: :json), summary_endpoint: Feature.enabled?(:build_report_summary, @project) ? summary_project_pipeline_tests_path(@project, @pipeline, format: :json) : '', + suite_endpoint: Feature.enabled?(:build_report_summary, @project) ? project_pipeline_test_path(@project, @pipeline, suite_name: ':suite_name', format: :json) : '', count_endpoint: test_reports_count_project_pipeline_path(@project, @pipeline, format: :json) } } = render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index b9952d6832f..a99c992af49 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -1,7 +1,7 @@ .file-content.code.js-syntax-highlight .line-numbers - if blob.data.present? - - link_icon = icon('link') + - link_icon = sprite_icon('link', size: 12) - link = blob_link if defined?(blob_link) - blob.data.each_line.each_with_index do |_, index| - offset = defined?(first_line_number) ? first_line_number : 1 diff --git a/babel.config.js b/babel.config.js index 6d377305e46..ea0f75a41ec 100644 --- a/babel.config.js +++ b/babel.config.js @@ -35,11 +35,6 @@ if (BABEL_ENV === 'coverage') { ]); } -// add rewire support when running tests -if (BABEL_ENV === 'karma' || BABEL_ENV === 'coverage') { - plugins.push('babel-plugin-rewire'); -} - // Jest is running in node environment, so we need additional plugins const isJest = Boolean(process.env.JEST_WORKER_ID); if (isJest) { diff --git a/changelogs/unreleased/37513-package-details-copy.yml b/changelogs/unreleased/37513-package-details-copy.yml new file mode 100644 index 00000000000..eac05fccb6e --- /dev/null +++ b/changelogs/unreleased/37513-package-details-copy.yml @@ -0,0 +1,5 @@ +--- +title: Update versions tab to other versions +merge_request: 37513 +author: +type: added diff --git a/changelogs/unreleased/cngo-show-mapped-user-in-jira-import-form-dropdown.yml b/changelogs/unreleased/cngo-show-mapped-user-in-jira-import-form-dropdown.yml new file mode 100644 index 00000000000..df5a8cb9ba0 --- /dev/null +++ b/changelogs/unreleased/cngo-show-mapped-user-in-jira-import-form-dropdown.yml @@ -0,0 +1,5 @@ +--- +title: Show mapped user in Jira import form dropdown +merge_request: 37575 +author: +type: added diff --git a/changelogs/unreleased/dimitrieh-master-patch-55257.yml b/changelogs/unreleased/dimitrieh-master-patch-55257.yml new file mode 100644 index 00000000000..dd71f33fe59 --- /dev/null +++ b/changelogs/unreleased/dimitrieh-master-patch-55257.yml @@ -0,0 +1,5 @@ +--- +title: Replace fa-link icons with GitLab SVG link icon +merge_request: 36973 +author: +type: other diff --git a/changelogs/unreleased/improve-matching-for-ci-job-entries.yml b/changelogs/unreleased/improve-matching-for-ci-job-entries.yml new file mode 100644 index 00000000000..4f7045f0d92 --- /dev/null +++ b/changelogs/unreleased/improve-matching-for-ci-job-entries.yml @@ -0,0 +1,5 @@ +--- +title: Show relevant error messages when failing to match a CI job entry +merge_request: 36536 +author: +type: fixed diff --git a/changelogs/unreleased/revert-2aafa3bb.yml b/changelogs/unreleased/revert-2aafa3bb.yml new file mode 100644 index 00000000000..3a19a3227df --- /dev/null +++ b/changelogs/unreleased/revert-2aafa3bb.yml @@ -0,0 +1,5 @@ +--- +title: Fix CSV downloads for multiple series in the same chart +merge_request: 37377 +author: +type: fixed diff --git a/doc/.vale/gitlab/Acronyms.yml b/doc/.vale/gitlab/Acronyms.yml index d166e71491c..c347c663bbf 100644 --- a/doc/.vale/gitlab/Acronyms.yml +++ b/doc/.vale/gitlab/Acronyms.yml @@ -68,6 +68,7 @@ exceptions: - SSH - SSL - SSO + - SVG - SVN - TCP - TIP diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md index 6e8dc2c6c57..63ae0e4193a 100644 --- a/doc/administration/high_availability/nfs.md +++ b/doc/administration/high_availability/nfs.md @@ -35,6 +35,20 @@ kernel version: If you are using that kernel version, be sure to upgrade GitLab to avoid errors. +## Fast lookup of authorized SSH keys + +The [fast SSH key lookup](../operations/fast_ssh_key_lookup.md) feature can improve +performance of GitLab instances even if they're using block storage. + +[Fast SSH key lookup](../operations/fast_ssh_key_lookup.md) is a replacement for +`authorized_keys` (in `/var/opt/gitlab/.ssh`) using the GitLab database. + +NFS increases latency, so fast lookup is recommended if `/var/opt/gitlab` +is moved to NFS. + +We are investigating the use of +[fast lookup as the default](https://gitlab.com/groups/gitlab-org/-/epics/3104). + ## NFS Server features ### Required features diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md index db18624d4e9..c97f4e51d30 100644 --- a/doc/ci/introduction/index.md +++ b/doc/ci/introduction/index.md @@ -230,7 +230,7 @@ syntax and with its attributes. This document [introduces the concepts of GitLab CI/CD in the scope of GitLab Pages](../../user/project/pages/getting_started/pages_from_scratch.md), for deploying static websites. Although it's meant for users who want to write their own Pages script from scratch, it also serves as an introduction to the setup process for GitLab CI/CD. -It covers the very first general steps of writing a CI/CD configuration +It covers the first general steps of writing a CI/CD configuration file, so we recommend you read through it to understand GitLab's CI/CD logic, and learn how to write your own script (or tweak an existing one) for any application. diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index 92e6add9f17..4ca13dc9e01 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -1204,3 +1204,7 @@ See the [schema reference](../api/graphql/reference/index.md) for details. This generated GraphQL documentation needs to be updated when the schema changes. For information on generating GraphQL documentation and schema files, see [updating the schema documentation](rake_tasks.md#update-graphql-documentation-and-schema-definitions). + +To help our readers, you should also add a new page to our [GraphQL API](../api/graphql/index.md) documentation. +For guidance, see the [GraphQL API](documentation/styleguide.md#graphql-api) section +of our documentation style guide. diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md index 651332b4e62..5fb5e1c1b13 100644 --- a/doc/development/api_styleguide.md +++ b/doc/development/api_styleguide.md @@ -13,7 +13,7 @@ Always use an [Entity](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/api/ ## Documentation -API endpoints must come with [documentation](documentation/styleguide.md#api), unless it is internal or behind a feature flag. +API endpoints must come with [documentation](documentation/styleguide.md#restful-api), unless it is internal or behind a feature flag. The docs should be in the same merge request, or, if strictly necessary, in a follow-up with the same milestone as the original merge request. diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index fb60a9b172c..d67fc696ad9 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -1135,48 +1135,39 @@ Usage examples: [Bootstrap utility class](https://getbootstrap.com/docs/4.4/utilities/float/): `**{tanuki, 32, float-right}**` renders as: **{tanuki, 32, float-right}** -### Use GitLab SVGs to describe UI elements +### When to use icons -When using GitLab SVGs to describe screen elements, also include the name or tooltip of the element as text. +Icons should be used sparingly, and only in ways that aid and do not hinder the readability of the +text. -For example, for references to the Admin Area: +For example, the following adds little to the accompanying text: -- Correct: `**{admin}** **Admin Area > Settings**` (**{admin}** **Admin Area > Settings**) -- Incorrect: `**{admin}** **> Settings**` (**{admin}** **> Settings**) +```markdown +1. Go to **{home}** **Project overview > Details** +``` -This will ensure that the source Markdown remains readable and should help with accessibility. +1. Go to **{home}** **Project overview > Details** -The following are examples of source Markdown for menu items with their published output: +However, the following might help the reader connect the text to the user interface: ```markdown -1. Go to **{home}** **Project overview > Details** -1. Go to **{doc-text}** **Repository > Branches** -1. Go to **{issues}** **Issues > List** -1. Go to **{merge-request}** **Merge Requests** -1. Go to **{rocket}** **CI/CD > Pipelines** -1. Go to **{shield}** **Security & Compliance > Configuration** -1. Go to **{cloud-gear}** **Operations > Metrics** -1. Go to **{package}** **Packages > Container Registry** -1. Go to **{chart}** **Project Analytics > Code Review** -1. Go to **{book}** **Wiki** -1. Go to **{snippet}** **Snippets** -1. Go to **{users}** **Members** -1. Select the **More actions** **{ellipsis_v}** icon > **Hide stage** +| Section | Description | +|:-------------------------|:----------------------------------------------------------------------------------------------------------------------------| +| **{overview}** Overview | View your GitLab Dashboard, and administer projects, users, groups, jobs, Runners, and Gitaly servers. | +| **{monitor}** Monitoring | View GitLab system information, and information on background jobs, logs, health checks, requests profiles, and audit logs. | +| **{messages}** Messages | Send and manage broadcast messages for your users. | ``` -1. Go to **{home}** **Project overview > Details** -1. Go to **{doc-text}** **Repository > Branches** -1. Go to **{issues}** **Issues > List** -1. Go to **{merge-request}** **Merge Requests** -1. Go to **{rocket}** **CI/CD > Pipelines** -1. Go to **{shield}** **Security & Compliance > Configuration** -1. Go to **{cloud-gear}** **Operations > Metrics** -1. Go to **{package}** **Packages > Container Registry** -1. Go to **{chart}** **Project Analytics > Code Review** -1. Go to **{book}** **Wiki** -1. Go to **{snippet}** **Snippets** -1. Go to **{users}** **Members** -1. Select the **More actions** **{ellipsis_v}** icon > **Hide stage** +| Section | Description | +|:-------------------------|:----------------------------------------------------------------------------------------------------------------------------| +| **{overview}** Overview | View your GitLab Dashboard, and administer projects, users, groups, jobs, Runners, and Gitaly servers. | +| **{monitor}** Monitoring | View GitLab system information, and information on background jobs, logs, health checks, requests profiles, and audit logs. | +| **{messages}** Messages | Send and manage broadcast messages for your users. | + +Use an icon when you find youself having to describe an interface element. For example: + +- Do: Click the Admin Area icon ( **{admin}** ). +- Don't: Click the Admin Area icon (the wrench icon). ## Alert boxes @@ -1622,10 +1613,10 @@ Learn how to [document features deployed behind flags](feature_flags.md). For guidance on developing GitLab with feature flags, see [Feature flags in development of GitLab](../feature_flags/index.md). -## API +## RESTful API -Here is a list of must-have items. Use them in the exact order that appears -on this document. Further explanation is given below. +Here is a list of must-have items for RESTful API documentation. Use them in the +exact order that appears on this document. Further explanation is given below. - Every method must have the REST API request. For example: @@ -1816,3 +1807,80 @@ exclude specific users when requesting a list of users for a project, you would ```shell curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --data "skip_users[]=<user_id>" --data "skip_users[]=<user_id>" https://gitlab.example.com/api/v4/projects/<project_id>/users ``` + +## GraphQL API + +GraphQL APIs are different from [RESTful APIs](#restful-api). Reference information is +generated automatically in our [GraphQL reference](../../api/graphql/reference/index.md). + +However, it's helpful to include examples on how to use GraphQL for different "use cases", +with samples that readers can use directly in the [GraphiQL explorer](../api_graphql_styleguide.md#graphiql). + +This section describes the steps required to add your GraphQL examples to GitLab documentation. + +### Add a dedicated GraphQL page + +To create a dedicated GraphQL page, create a new `.md` file in the `doc/api/graphql/` directory. +Give that file a functional name, such as `import_from_specific_location.md`. + +### Start the page with an explanation + +Include a page title that describes the GraphQL functionality in a few words, such as: + +```markdown +# Search for [substitute kind of data] +``` + +Describe the search. One sentence may be all you need. More information may help +readers learn how to use the example for their GitLab deployments. + +### Include a procedure using the GraphiQL explorer + +The GraphiQL explorer can help readers test queries with working deployments. Set up the section with the following: + +- Use the following title: + + ```markdown + ## Set up the GraphiQL explorer + ``` + +- Include a code block with the query that anyone can include in their instance of + the GraphiQL explorer: + + ````markdown + ```graphql + query { + <insert queries here> + } + ``` + ```` + +- Tell the user what to do: + + ```markdown + 1. Open the GraphiQL explorer tool in the following URL: `https://gitlab.com/-/graphql-explorer`. + 1. Paste the `query` listed above into the left window of your GraphiQL explorer tool. + 1. Click Play to get the result shown here: + ``` + +- Include a screenshot of the result in the GraphiQL explorer. Follow the naming + convention described in the [Save the image](#save-the-image) section. +- Follow up with an example of what you can do with the output. + Make sure the example is something that readers can do on their own deployments. +- Include a link to the [GraphQL API resources](../../api/graphql/reference/index.md). + +### Add the GraphQL example to the Table of Contents + +You'll need to open a second MR, against the [GitLab Docs repository](https://gitlab.com/gitlab-org/gitlab-docs/). + +We store our Table of Contents in the `default-nav.yaml` file, in the `content/_data` +subdirectory. You can find the GraphQL section under the following line: + +```yaml + - category_title: GraphQL +``` + +Be aware that CI tests for that second MR will fail with a bad link until the main MR +that adds the new GraphQL page is merged. + +And that's all you need! diff --git a/doc/development/geo/framework.md b/doc/development/geo/framework.md index 5c9b01466d6..c5ba926dd04 100644 --- a/doc/development/geo/framework.md +++ b/doc/development/geo/framework.md @@ -360,11 +360,33 @@ Widgets should now be replicated by Geo! DOWNTIME = false def change - add_column :widgets, :verification_retry_at, :datetime_with_timezone - add_column :widgets, :verified_at, :datetime_with_timezone - add_column :widgets, :verification_checksum, :binary, using: 'verification_checksum::bytea' - add_column :widgets, :verification_failure, :string - add_column :widgets, :verification_retry_count, :integer + change_table(:widgets) do |t| + t.integer :verification_retry_count, limit: 2 + t.column :verification_retry_at, :datetime_with_timezone + t.column :verified_at, :datetime_with_timezone + t.binary :verification_checksum, using: 'verification_checksum::bytea' + + # rubocop:disable Migration/AddLimitToTextColumns + t.text :verification_failure + # rubocop:enable Migration/AddLimitToTextColumns + end + end + end + ``` + + Adding a `text` column also [requires](../database/strings_and_the_text_data_type.md#add-a-text-column-to-an-existing-table) + setting a limit: + + ```ruby + # frozen_string_literal: true + + class AddVerificationFailureLimitToWidgets < ActiveRecord::Migration[6.0] + DOWNTIME = false + + disable_ddl_transaction! + + def change + add_text_limit :widgets, :verification_failure, 255 end end ``` diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md index 507ba25850d..90f4782e0be 100644 --- a/doc/user/clusters/applications.md +++ b/doc/user/clusters/applications.md @@ -311,7 +311,7 @@ This feature: For example: ```shell - kubectl logs -n gitlab-managed-apps $(kubectl get pod -n gitlab-managed-apps -l app=nginx-ingress,component=controller --no-headers=true -o custom-columns=:metadata.name) modsecurity-log -f + kubectl -n gitlab-managed-apps logs -l app=nginx-ingress,component=controller -c modsecurity-log -f ``` To enable WAF, switch its respective toggle to the enabled position when installing or updating [Ingress application](#ingress). @@ -1004,7 +1004,7 @@ The Cilium monitor log for traffic is logged out by the `cilium-monitor` sidecar container. You can check these logs with the following command: ```shell -kubectl -n gitlab-managed-apps logs cilium-XXXX cilium-monitor +kubectl -n gitlab-managed-apps logs -l k8s-app=cilium -c cilium-monitor ``` You can disable the monitor log in `.gitlab/managed-apps/cilium/values.yaml`: @@ -1127,7 +1127,7 @@ falco: You can check these logs with the following command: ```shell -kubectl logs -l app=falco -n gitlab-managed-apps +kubectl -n gitlab-managed-apps logs -l app=falco ``` NOTE: **Note:** diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md index 9886ef91f16..5bb31d54e08 100644 --- a/doc/user/project/labels.md +++ b/doc/user/project/labels.md @@ -43,7 +43,7 @@ To assign a label to an issue, merge request or epic: click on them. You can search repeatedly and add more labels. 1. Click **X** or anywhere outside the label section and the labels are applied. -You can also assign a label with the [`/assign @username` quick action](quick_actions.md). +You can also assign a label with the [`/label ~label1 ~label2` quick action](quick_actions.md). ## Label management diff --git a/lib/bitbucket/representation/repo.rb b/lib/bitbucket/representation/repo.rb index c5bfc91e43d..fa4780dd8de 100644 --- a/lib/bitbucket/representation/repo.rb +++ b/lib/bitbucket/representation/repo.rb @@ -3,8 +3,6 @@ module Bitbucket module Representation class Repo < Representation::Base - attr_reader :owner, :slug - def initialize(raw) super(raw) end diff --git a/lib/declarative_policy/base.rb b/lib/declarative_policy/base.rb index 4af0251b990..49cbdd2aeb4 100644 --- a/lib/declarative_policy/base.rb +++ b/lib/declarative_policy/base.rb @@ -213,7 +213,7 @@ module DeclarativePolicy # # It also stores a reference to the cache, so it can be used # to cache computations by e.g. ManifestCondition. - attr_reader :user, :subject, :cache + attr_reader :user, :subject def initialize(user, subject, opts = {}) @user = user @subject = subject diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb index b3321c0b1fb..8c5000147c4 100644 --- a/lib/gitlab/auth/ldap/person.rb +++ b/lib/gitlab/auth/ldap/person.rb @@ -11,7 +11,7 @@ module Gitlab InvalidEntryError = Class.new(StandardError) - attr_accessor :entry, :provider + attr_accessor :provider def self.find_by_uid(uid, adapter) uid = Net::LDAP::Filter.escape(uid) diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb index 8a60d6ef482..bdfef723da8 100644 --- a/lib/gitlab/auth/o_auth/user.rb +++ b/lib/gitlab/auth/o_auth/user.rb @@ -12,7 +12,7 @@ module Gitlab SignupDisabledError = Class.new(StandardError) SigninDisabledForProviderError = Class.new(StandardError) - attr_accessor :auth_hash, :gl_user + attr_reader :auth_hash def initialize(auth_hash) self.auth_hash = auth_hash diff --git a/lib/gitlab/ci/build/artifacts/metadata/entry.rb b/lib/gitlab/ci/build/artifacts/metadata/entry.rb index ef354832e8e..355fffbf9c6 100644 --- a/lib/gitlab/ci/build/artifacts/metadata/entry.rb +++ b/lib/gitlab/ci/build/artifacts/metadata/entry.rb @@ -16,7 +16,7 @@ module Gitlab # class Entry attr_reader :entries - attr_accessor :name + attr_writer :name def initialize(path, entries) @entries = entries diff --git a/lib/gitlab/ci/config/entry/bridge.rb b/lib/gitlab/ci/config/entry/bridge.rb index f4362d3b0ce..a8b67a1db4f 100644 --- a/lib/gitlab/ci/config/entry/bridge.rb +++ b/lib/gitlab/ci/config/entry/bridge.rb @@ -11,7 +11,7 @@ module Gitlab class Bridge < ::Gitlab::Config::Entry::Node include ::Gitlab::Ci::Config::Entry::Processable - ALLOWED_KEYS = %i[trigger allow_failure when needs].freeze + ALLOWED_KEYS = %i[trigger].freeze validations do validates :config, allowed_keys: ALLOWED_KEYS + PROCESSABLE_ALLOWED_KEYS diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 4a18f8ac8b5..bd14ad78012 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -11,9 +11,8 @@ module Gitlab include ::Gitlab::Ci::Config::Entry::Processable ALLOWED_WHEN = %w[on_success on_failure always manual delayed].freeze - ALLOWED_KEYS = %i[tags script type image services - allow_failure type when start_in artifacts cache - dependencies before_script needs after_script + ALLOWED_KEYS = %i[tags script type image services start_in artifacts + cache dependencies before_script after_script environment coverage retry parallel interruptible timeout resource_group release secrets].freeze @@ -129,9 +128,39 @@ module Gitlab :needs, :retry, :parallel, :start_in, :interruptible, :timeout, :resource_group, :release + Matcher = Struct.new(:name, :config) do + def applies? + job_is_not_hidden? && + config_is_a_hash? && + has_job_keys? + end + + private + + def job_is_not_hidden? + !name.to_s.start_with?('.') + end + + def config_is_a_hash? + config.is_a?(Hash) + end + + def has_job_keys? + if name == :default + config.key?(:script) + else + (ALLOWED_KEYS & config.keys).any? + end + end + end + def self.matching?(name, config) - !name.to_s.start_with?('.') && - config.is_a?(Hash) && config.key?(:script) + if Gitlab::Ci::Features.job_entry_matches_all_keys? + Matcher.new(name, config).applies? + else + !name.to_s.start_with?('.') && + config.is_a?(Hash) && config.key?(:script) + end end def self.visible? diff --git a/lib/gitlab/ci/config/entry/processable.rb b/lib/gitlab/ci/config/entry/processable.rb index b4539475d88..c5005172224 100644 --- a/lib/gitlab/ci/config/entry/processable.rb +++ b/lib/gitlab/ci/config/entry/processable.rb @@ -14,7 +14,8 @@ module Gitlab include ::Gitlab::Config::Entry::Attributable include ::Gitlab::Config::Entry::Inheritable - PROCESSABLE_ALLOWED_KEYS = %i[extends stage only except rules variables inherit].freeze + PROCESSABLE_ALLOWED_KEYS = %i[extends stage only except rules variables + inherit allow_failure when needs].freeze included do validations do diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index dbde50cf592..6d433c07abb 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -77,6 +77,10 @@ module Gitlab def self.allow_to_create_merge_request_pipelines_in_target_project?(target_project) ::Feature.enabled?(:ci_allow_to_create_merge_request_pipelines_in_target_project, target_project) end + + def self.job_entry_matches_all_keys? + ::Feature.enabled?(:ci_job_entry_matches_all_keys) + end end end end diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb index 07ae430c45e..6c6dd90e450 100644 --- a/lib/gitlab/cycle_analytics/base_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/base_event_fetcher.rb @@ -6,7 +6,7 @@ module Gitlab include BaseQuery include GroupProjectsProvider - attr_reader :projections, :query, :stage, :order, :options + attr_reader :projections, :query, :stage, :options MAX_EVENTS = 50 diff --git a/lib/gitlab/diff/formatters/base_formatter.rb b/lib/gitlab/diff/formatters/base_formatter.rb index 31eeadc45f7..e24150a2330 100644 --- a/lib/gitlab/diff/formatters/base_formatter.rb +++ b/lib/gitlab/diff/formatters/base_formatter.rb @@ -10,7 +10,6 @@ module Gitlab attr_reader :base_sha attr_reader :start_sha attr_reader :head_sha - attr_reader :position_type def initialize(attrs) if diff_file = attrs[:diff_file] diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index 1b49d356d29..4db0c2bce10 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -25,7 +25,8 @@ module Gitlab LFS_POINTER_MIN_SIZE = 120.bytes LFS_POINTER_MAX_SIZE = 200.bytes - attr_accessor :name, :path, :size, :data, :mode, :id, :commit_id, :loaded_size, :binary + attr_accessor :size, :mode, :id, :commit_id, :loaded_size, :binary + attr_writer :name, :path, :data define_counter :gitlab_blob_truncated_true do docstring 'blob.truncated? == true' diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index ea7a6e84195..16f59a96c6d 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -44,7 +44,7 @@ module Gitlab # Relative path of repo attr_reader :relative_path - attr_reader :storage, :gl_repository, :relative_path, :gl_project_path + attr_reader :storage, :gl_repository, :gl_project_path # This remote name has to be stable for all types of repositories that # can join an object pool. If it's structure ever changes, a migration diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb index 7e072c5db50..ed02f2e92ec 100644 --- a/lib/gitlab/git/tree.rb +++ b/lib/gitlab/git/tree.rb @@ -6,8 +6,8 @@ module Gitlab include Gitlab::EncodingHelper extend Gitlab::Git::WrapsGitalyErrors - attr_accessor :id, :root_id, :name, :path, :flat_path, :type, - :mode, :commit_id, :submodule_url + attr_accessor :id, :root_id, :type, :mode, :commit_id, :submodule_url + attr_writer :name, :path, :flat_path class << self # Get list of tree objects diff --git a/lib/gitlab/git/wiki_page.rb b/lib/gitlab/git/wiki_page.rb index f6cac398548..a1f3d64ccde 100644 --- a/lib/gitlab/git/wiki_page.rb +++ b/lib/gitlab/git/wiki_page.rb @@ -3,7 +3,7 @@ module Gitlab module Git class WikiPage - attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :text_data, :historical, :formatted_data + attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :historical, :formatted_data # This class abstracts away Gitlab::GitalyClient::WikiPage def initialize(gitaly_page, version) diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index 2889dbc68cc..d55906083ff 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -8,11 +8,11 @@ module Gitlab class << self def enabled? - config.enabled && config.address + config.enabled && config.address.present? end def supports_wildcard? - config.address && config.address.include?(WILDCARD_PLACEHOLDER) + config.address.present? && config.address.include?(WILDCARD_PLACEHOLDER) end def supports_issue_creation? diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 45a6e781638..461079c2054 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16516,6 +16516,9 @@ msgstr "" msgid "Other merge requests block this MR" msgstr "" +msgid "Other versions" +msgstr "" + msgid "Other visibility settings have been disabled by the administrator." msgstr "" @@ -23383,6 +23386,9 @@ msgstr "" msgid "TestReports|There was an error fetching the test reports." msgstr "" +msgid "TestReports|There was an error fetching the test suite." +msgstr "" + msgid "Tests" msgstr "" @@ -26193,9 +26199,6 @@ msgstr "" msgid "Version" msgstr "" -msgid "Versions" -msgstr "" - msgid "View Documentation" msgstr "" diff --git a/package.json b/package.json index 164ad3b4c56..c6371ae3e59 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,6 @@ "babel-jest": "^24.1.0", "babel-plugin-dynamic-import-node": "^2.2.0", "babel-plugin-istanbul": "^5.1.0", - "babel-plugin-rewire": "^1.2.0", "chalk": "^2.4.1", "commander": "^2.18.0", "custom-jquery-matchers": "^2.1.0", diff --git a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb index 6bd6634822c..a65a82fab43 100644 --- a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb +++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb @@ -50,7 +50,7 @@ RSpec.describe 'Blob button line permalinks (BlobLinePermalinkUpdater)', :js do visit_blob find("##{ending_fragment}").hover - find("##{ending_fragment} i").click + find("##{ending_fragment} svg").click expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment))) end @@ -100,7 +100,7 @@ RSpec.describe 'Blob button line permalinks (BlobLinePermalinkUpdater)', :js do visit_blob find("##{ending_fragment}").hover - find("##{ending_fragment} i").click + find("##{ending_fragment} svg").click expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment))) end diff --git a/spec/frontend/helpers/monitor_helper_spec.js b/spec/frontend/helpers/monitor_helper_spec.js index 083b6404125..219b05e312b 100644 --- a/spec/frontend/helpers/monitor_helper_spec.js +++ b/spec/frontend/helpers/monitor_helper_spec.js @@ -1,12 +1,38 @@ -import * as monitorHelper from '~/helpers/monitor_helper'; +import { getSeriesLabel, makeDataSeries } from '~/helpers/monitor_helper'; describe('monitor helper', () => { const defaultConfig = { default: true, name: 'default name' }; const name = 'data name'; const series = [[1, 1], [2, 2], [3, 3]]; - const data = ({ metric = { default_name: name }, values = series } = {}) => [{ metric, values }]; + + describe('getSeriesLabel', () => { + const metricAttributes = { __name__: 'up', app: 'prometheus' }; + + it('gets a single attribute label', () => { + expect(getSeriesLabel('app', metricAttributes)).toBe('app: prometheus'); + }); + + it('gets a templated label', () => { + expect(getSeriesLabel('{{__name__}}', metricAttributes)).toBe('up'); + expect(getSeriesLabel('{{app}}', metricAttributes)).toBe('prometheus'); + expect(getSeriesLabel('{{missing}}', metricAttributes)).toBe('{{missing}}'); + }); + + it('gets a multiple label', () => { + expect(getSeriesLabel(null, metricAttributes)).toBe('__name__: up, app: prometheus'); + expect(getSeriesLabel('', metricAttributes)).toBe('__name__: up, app: prometheus'); + }); + + it('gets a simple label', () => { + expect(getSeriesLabel('A label', {})).toBe('A label'); + }); + }); describe('makeDataSeries', () => { + const data = ({ metric = { default_name: name }, values = series } = {}) => [ + { metric, values }, + ]; + const expectedDataSeries = [ { ...defaultConfig, @@ -15,19 +41,17 @@ describe('monitor helper', () => { ]; it('converts query results to data series', () => { - expect(monitorHelper.makeDataSeries(data({ metric: {} }), defaultConfig)).toEqual( - expectedDataSeries, - ); + expect(makeDataSeries(data({ metric: {} }), defaultConfig)).toEqual(expectedDataSeries); }); it('returns an empty array if no query results exist', () => { - expect(monitorHelper.makeDataSeries([], defaultConfig)).toEqual([]); + expect(makeDataSeries([], defaultConfig)).toEqual([]); }); it('handles multi-series query results', () => { const expectedData = { ...expectedDataSeries[0], name: 'default name: data name' }; - expect(monitorHelper.makeDataSeries([...data(), ...data()], defaultConfig)).toEqual([ + expect(makeDataSeries([...data(), ...data()], defaultConfig)).toEqual([ expectedData, expectedData, ]); @@ -39,10 +63,7 @@ describe('monitor helper', () => { name: '{{cmd}}', }; - const [result] = monitorHelper.makeDataSeries( - [{ metric: { cmd: 'brpop' }, values: series }], - config, - ); + const [result] = makeDataSeries([{ metric: { cmd: 'brpop' }, values: series }], config); expect(result.name).toEqual('brpop'); }); @@ -53,7 +74,7 @@ describe('monitor helper', () => { name: '', }; - const [result] = monitorHelper.makeDataSeries( + const [result] = makeDataSeries( [ { metric: { @@ -79,7 +100,7 @@ describe('monitor helper', () => { name: 'backend: {{ backend }}', }; - const [result] = monitorHelper.makeDataSeries( + const [result] = makeDataSeries( [{ metric: { backend: 'HA Server' }, values: series }], config, ); @@ -90,10 +111,7 @@ describe('monitor helper', () => { it('supports repeated template variables', () => { const config = { ...defaultConfig, name: '{{cmd}}, {{cmd}}' }; - const [result] = monitorHelper.makeDataSeries( - [{ metric: { cmd: 'brpop' }, values: series }], - config, - ); + const [result] = makeDataSeries([{ metric: { cmd: 'brpop' }, values: series }], config); expect(result.name).toEqual('brpop, brpop'); }); @@ -101,7 +119,7 @@ describe('monitor helper', () => { it('supports hyphenated template variables', () => { const config = { ...defaultConfig, name: 'expired - {{ test-attribute }}' }; - const [result] = monitorHelper.makeDataSeries( + const [result] = makeDataSeries( [{ metric: { 'test-attribute': 'test-attribute-value' }, values: series }], config, ); @@ -115,7 +133,7 @@ describe('monitor helper', () => { name: '{{job}}: {{cmd}}', }; - const [result] = monitorHelper.makeDataSeries( + const [result] = makeDataSeries( [{ metric: { cmd: 'brpop', job: 'redis' }, values: series }], config, ); @@ -129,7 +147,7 @@ describe('monitor helper', () => { name: '{{cmd}}', }; - const [firstSeries, secondSeries] = monitorHelper.makeDataSeries( + const [firstSeries, secondSeries] = makeDataSeries( [ { metric: { cmd: 'brpop' }, values: series }, { metric: { cmd: 'zrangebyscore' }, values: series }, diff --git a/spec/frontend/jira_import/components/jira_import_form_spec.js b/spec/frontend/jira_import/components/jira_import_form_spec.js index 685b0288e92..12d161edf72 100644 --- a/spec/frontend/jira_import/components/jira_import_form_spec.js +++ b/spec/frontend/jira_import/components/jira_import_form_spec.js @@ -1,10 +1,10 @@ -import { GlButton, GlFormSelect, GlLabel, GlTable } from '@gitlab/ui'; +import { GlButton, GlNewDropdown, GlFormSelect, GlLabel, GlTable } from '@gitlab/ui'; import { getByRole } from '@testing-library/dom'; import { mount, shallowMount } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import JiraImportForm from '~/jira_import/components/jira_import_form.vue'; -import { issuesPath, jiraProjects, userMappings } from '../mock_data'; +import { issuesPath, jiraProjects, userMappings as defaultUserMappings } from '../mock_data'; describe('JiraImportForm', () => { let axiosMock; @@ -16,11 +16,21 @@ describe('JiraImportForm', () => { const getSelectDropdown = () => wrapper.find(GlFormSelect); + const getContinueButton = () => wrapper.find(GlButton); + const getCancelButton = () => wrapper.findAll(GlButton).at(1); + const getTable = () => wrapper.find(GlTable); + + const getUserDropdown = () => getTable().find(GlNewDropdown); + const getHeader = name => getByRole(wrapper.element, 'columnheader', { name }); - const mountComponent = ({ isSubmitting = false, mountFunction = shallowMount } = {}) => + const mountComponent = ({ + isSubmitting = false, + userMappings = defaultUserMappings, + mountFunction = shallowMount, + } = {}) => mountFunction(JiraImportForm, { propsData: { importLabel, @@ -121,13 +131,53 @@ describe('JiraImportForm', () => { it('shows all user mappings', () => { wrapper = mountComponent({ mountFunction: mount }); - expect(wrapper.find(GlTable).findAll('tbody tr').length).toBe(userMappings.length); + expect(getTable().findAll('tbody tr')).toHaveLength(2); }); it('shows correct information in each cell', () => { wrapper = mountComponent({ mountFunction: mount }); - expect(wrapper.find(GlTable).element).toMatchSnapshot(); + expect(getTable().element).toMatchSnapshot(); + }); + + describe('when there is no Jira->GitLab user mapping', () => { + it('shows the logged in user in the dropdown', () => { + wrapper = mountComponent({ + mountFunction: mount, + userMappings: [ + { + jiraAccountId: 'aei23f98f-q23fj98qfj', + jiraDisplayName: 'Jane Doe', + jiraEmail: 'janedoe@example.com', + gitlabId: undefined, + gitlabUsername: undefined, + }, + ], + }); + + expect(getUserDropdown().text()).toContain(currentUsername); + }); + }); + + describe('when there is a Jira->GitLab user mapping', () => { + it('shows the mapped user in the dropdown', () => { + const gitlabUsername = 'mai'; + + wrapper = mountComponent({ + mountFunction: mount, + userMappings: [ + { + jiraAccountId: 'aei23f98f-q23fj98qfj', + jiraDisplayName: 'Jane Doe', + jiraEmail: 'janedoe@example.com', + gitlabId: 14, + gitlabUsername, + }, + ], + }); + + expect(getUserDropdown().text()).toContain(gitlabUsername); + }); }); }); }); @@ -137,13 +187,13 @@ describe('JiraImportForm', () => { it('is shown', () => { wrapper = mountComponent(); - expect(wrapper.find(GlButton).text()).toBe('Continue'); + expect(getContinueButton().text()).toBe('Continue'); }); it('is in loading state when the form is submitting', async () => { wrapper = mountComponent({ isSubmitting: true }); - expect(wrapper.find(GlButton).props('loading')).toBe(true); + expect(getContinueButton().props('loading')).toBe(true); }); }); diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js index 507598ad3aa..a38af9770cf 100644 --- a/spec/frontend/monitoring/components/dashboard_panel_spec.js +++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js @@ -443,7 +443,7 @@ describe('Dashboard Panel', () => { describe('csvText', () => { it('converts metrics data from json to csv', () => { - const header = `timestamp,${graphData.y_label}`; + const header = `timestamp,"${graphData.y_label} > ${graphData.metrics[0].label}"`; const data = graphData.metrics[0].result[0].values; const firstRow = `${data[0][0]},${data[0][1]}`; const secondRow = `${data[1][0]},${data[1][1]}`; diff --git a/spec/frontend/monitoring/csv_export_spec.js b/spec/frontend/monitoring/csv_export_spec.js new file mode 100644 index 00000000000..eb2a6e40243 --- /dev/null +++ b/spec/frontend/monitoring/csv_export_spec.js @@ -0,0 +1,126 @@ +import { timeSeriesGraphData } from './graph_data'; +import { graphDataToCsv } from '~/monitoring/csv_export'; + +describe('monitoring export_csv', () => { + describe('graphDataToCsv', () => { + const expectCsvToMatchLines = (csv, lines) => expect(`${lines.join('\r\n')}\r\n`).toEqual(csv); + + it('should return a csv with 0 metrics', () => { + const data = timeSeriesGraphData({}, { metricCount: 0 }); + + expect(graphDataToCsv(data)).toEqual(''); + }); + + it('should return a csv with 1 metric with no data', () => { + const data = timeSeriesGraphData({}, { metricCount: 1 }); + + // When state is NO_DATA, result is null + data.metrics[0].result = null; + + expect(graphDataToCsv(data)).toEqual(''); + }); + + it('should return a csv with 1 metric', () => { + const data = timeSeriesGraphData({}, { metricCount: 1 }); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > Metric 1"`, + '2015-07-01T20:10:50.000Z,1', + '2015-07-01T20:12:50.000Z,2', + '2015-07-01T20:14:50.000Z,3', + ]); + }); + + it('should return a csv with multiple metrics and one with no data', () => { + const data = timeSeriesGraphData({}, { metricCount: 2 }); + + // When state is NO_DATA, result is null + data.metrics[0].result = null; + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > Metric 2"`, + '2015-07-01T20:10:50.000Z,1', + '2015-07-01T20:12:50.000Z,2', + '2015-07-01T20:14:50.000Z,3', + ]); + }); + + it('should return a csv when not all metrics have the same timestamps', () => { + const data = timeSeriesGraphData({}, { metricCount: 3 }); + + // Add an "odd" timestamp that is not in the dataset + Object.assign(data.metrics[2].result[0], { + value: ['2016-01-01T00:00:00.000Z', 9], + values: [['2016-01-01T00:00:00.000Z', 9]], + }); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > Metric 1","Y Axis > Metric 2","Y Axis > Metric 3"`, + '2015-07-01T20:10:50.000Z,1,1,', + '2015-07-01T20:12:50.000Z,2,2,', + '2015-07-01T20:14:50.000Z,3,3,', + '2016-01-01T00:00:00.000Z,,,9', + ]); + }); + + it('should escape double quotes in metric labels with two double quotes ("")', () => { + const data = timeSeriesGraphData({}, { metricCount: 1 }); + + data.metrics[0].label = 'My "quoted" metric'; + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > My ""quoted"" metric"`, + '2015-07-01T20:10:50.000Z,1', + '2015-07-01T20:12:50.000Z,2', + '2015-07-01T20:14:50.000Z,3', + ]); + }); + + it('should return a csv with multiple metrics', () => { + const data = timeSeriesGraphData({}, { metricCount: 3 }); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > Metric 1","Y Axis > Metric 2","Y Axis > Metric 3"`, + '2015-07-01T20:10:50.000Z,1,1,1', + '2015-07-01T20:12:50.000Z,2,2,2', + '2015-07-01T20:14:50.000Z,3,3,3', + ]); + }); + + it('should return a csv with 1 metric and multiple series with labels', () => { + const data = timeSeriesGraphData({}, { isMultiSeries: true }); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > Metric 1","Y Axis > Metric 1"`, + '2015-07-01T20:10:50.000Z,1,4', + '2015-07-01T20:12:50.000Z,2,5', + '2015-07-01T20:14:50.000Z,3,6', + ]); + }); + + it('should return a csv with 1 metric and multiple series', () => { + const data = timeSeriesGraphData({}, { isMultiSeries: true, withLabels: false }); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091"`, + '2015-07-01T20:10:50.000Z,1,4', + '2015-07-01T20:12:50.000Z,2,5', + '2015-07-01T20:14:50.000Z,3,6', + ]); + }); + + it('should return a csv with multiple metrics and multiple series', () => { + const data = timeSeriesGraphData( + {}, + { metricCount: 3, isMultiSeries: true, withLabels: false }, + ); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091","Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091","Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091"`, + '2015-07-01T20:10:50.000Z,1,4,1,4,1,4', + '2015-07-01T20:12:50.000Z,2,5,2,5,2,5', + '2015-07-01T20:14:50.000Z,3,6,3,6,3,6', + ]); + }); + }); +}); diff --git a/spec/frontend/monitoring/graph_data.js b/spec/frontend/monitoring/graph_data.js index 8e81ad7a585..fcdca95ac09 100644 --- a/spec/frontend/monitoring/graph_data.js +++ b/spec/frontend/monitoring/graph_data.js @@ -83,7 +83,7 @@ const matrixMultiResult = ({ values1 = ['1', '2', '3'], values2 = ['4', '5', '6' * @param {Object} dataOptions.isMultiSeries */ export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => { - const { metricCount = 1, isMultiSeries = false } = dataOptions; + const { metricCount = 1, isMultiSeries = false, withLabels = true } = dataOptions; return mapPanelToViewModel({ title: 'Time Series Panel', @@ -91,7 +91,7 @@ export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => { x_label: 'X Axis', y_label: 'Y Axis', metrics: Array.from(Array(metricCount), (_, i) => ({ - label: `Metric ${i + 1}`, + label: withLabels ? `Metric ${i + 1}` : undefined, state: metricStates.OK, result: isMultiSeries ? matrixMultiResult() : matrixSingleResult(), })), diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js index d4647c55a53..41175959de5 100644 --- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js @@ -17,9 +17,11 @@ describe('Actions TestReports Store', () => { const summary = { total_count: 1 }; const fullReportEndpoint = `${TEST_HOST}/test_reports.json`; + const suiteEndpoint = `${TEST_HOST}/tests/:suite_name.json`; const summaryEndpoint = `${TEST_HOST}/test_reports/summary.json`; const defaultState = { fullReportEndpoint, + suiteEndpoint, summaryEndpoint, testReports: {}, selectedSuite: null, @@ -100,6 +102,65 @@ describe('Actions TestReports Store', () => { }); }); + describe('fetch test suite', () => { + beforeEach(() => { + const buildIds = [1]; + testReports.test_suites[0].build_ids = buildIds; + const endpoint = suiteEndpoint.replace(':suite_name', testReports.test_suites[0].name); + mock + .onGet(endpoint, { params: { build_ids: buildIds } }) + .replyOnce(200, testReports.test_suites[0], {}); + }); + + it('sets test suite and shows tests', done => { + const suite = testReports.test_suites[0]; + const index = 0; + + testAction( + actions.fetchTestSuite, + index, + { ...state, testReports }, + [{ type: types.SET_SUITE, payload: { suite, index } }], + [{ type: 'toggleLoading' }, { type: 'toggleLoading' }], + done, + ); + }); + + it('should create flash on API error', done => { + const index = 0; + + testAction( + actions.fetchTestSuite, + index, + { ...state, testReports, suiteEndpoint: null }, + [], + [{ type: 'toggleLoading' }, { type: 'toggleLoading' }], + () => { + expect(createFlash).toHaveBeenCalled(); + done(); + }, + ); + }); + + describe('when we already have the suite data', () => { + it('should not fetch suite', done => { + const index = 0; + testReports.test_suites[0].hasFullSuite = true; + + testAction(actions.fetchTestSuite, index, { ...state, testReports }, [], [], done); + }); + }); + + describe('when we already have the full report data', () => { + it('should not fetch suite', done => { + const index = 0; + testReports.hasFullReport = true; + + testAction(actions.fetchTestSuite, index, { ...state, testReports }, [], [], done); + }); + }); + }); + describe('fetch full report', () => { beforeEach(() => { mock.onGet(fullReportEndpoint).replyOnce(200, testReports, {}); diff --git a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js index f4cc5c4bc5d..70fd048b9c6 100644 --- a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js @@ -29,6 +29,21 @@ describe('Mutations TestReports Store', () => { }); }); + describe('set suite', () => { + it('should set the suite at the given index', () => { + mockState.testReports = testReports; + const suite = { name: 'test_suite' }; + const index = 0; + const expectedState = { ...mockState }; + expectedState.testReports.test_suites[index] = { suite, hasFullSuite: true }; + mutations[types.SET_SUITE](mockState, { suite, index }); + + expect(mockState.testReports.test_suites[index]).toEqual( + expectedState.testReports.test_suites[index], + ); + }); + }); + describe('set selected suite index', () => { it('should set selectedSuiteIndex', () => { const selectedSuiteIndex = 0; diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js index ef0bcffabe3..a709edf5184 100644 --- a/spec/frontend/pipelines/test_reports/test_reports_spec.js +++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js @@ -22,7 +22,7 @@ describe('Test reports app', () => { const testSummaryTable = () => wrapper.find(TestSummaryTable); const actionSpies = { - fetchFullReport: jest.fn(), + fetchTestSuite: jest.fn(), fetchSummary: jest.fn(), setSelectedSuiteIndex: jest.fn(), removeSelectedSuiteIndex: jest.fn(), @@ -91,28 +91,14 @@ describe('Test reports app', () => { }); describe('when a suite is clicked', () => { - describe('when the full test report has already been received', () => { - beforeEach(() => { - createComponent({ hasFullReport: true }); - testSummaryTable().vm.$emit('row-click', 0); - }); - - it('should only call setSelectedSuiteIndex', () => { - expect(actionSpies.setSelectedSuiteIndex).toHaveBeenCalled(); - expect(actionSpies.fetchFullReport).not.toHaveBeenCalled(); - }); + beforeEach(() => { + createComponent({ hasFullReport: true }); + testSummaryTable().vm.$emit('row-click', 0); }); - describe('when the full test report has not been received', () => { - beforeEach(() => { - createComponent({ hasFullReport: false }); - testSummaryTable().vm.$emit('row-click', 0); - }); - - it('should call setSelectedSuiteIndex and fetchFullReport', () => { - expect(actionSpies.setSelectedSuiteIndex).toHaveBeenCalled(); - expect(actionSpies.fetchFullReport).toHaveBeenCalled(); - }); + it('should call setSelectedSuiteIndex and fetchTestSuite', () => { + expect(actionSpies.setSelectedSuiteIndex).toHaveBeenCalled(); + expect(actionSpies.fetchTestSuite).toHaveBeenCalled(); }); }); diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 49eae715a45..544c19da57b 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -24,7 +24,7 @@ afterEach(() => }), ); -initializeTestTimeout(process.env.CI ? 5000 : 500); +initializeTestTimeout(process.env.CI ? 6000 : 500); Vue.config.devtools = false; Vue.config.productionTip = false; diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index b81804def57..b37a53515a6 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -1,5 +1,5 @@ /* eslint-disable - jasmine/no-global-setup, jasmine/no-unsafe-spy, no-underscore-dangle, no-console + jasmine/no-global-setup, no-underscore-dangle, no-console */ import $ from 'jquery'; @@ -81,17 +81,6 @@ window.addEventListener('unhandledrejection', event => { console.error(event.reason.stack || event.reason); }); -// Add global function to spy on a module's dependencies via rewire -window.spyOnDependency = (module, name) => { - const dependency = module.__GetDependency__(name); - const spy = jasmine.createSpy(name, dependency); - module.__Rewire__(name, spy); - return spy; -}; - -// Reset any rewired modules after each test (see babel-plugin-rewire) -afterEach(__rewire_reset_all__); // eslint-disable-line - // HACK: Chrome 59 disconnects if there are too many synchronous tests in a row // because it appears to lock up the thread that communicates to Karma's socket // This async beforeEach gets called on every spec and releases the JS thread long diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 9b1212fee44..ca02eaee0a0 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -73,6 +73,45 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do it { is_expected.to be_falsey } end + + context 'when config does not contain script' do + let(:name) { :build } + + let(:config) do + { before_script: "cd ${PROJ_DIR} " } + end + + it { is_expected.to be_truthy } + end + + context 'when using the default job without script' do + let(:name) { :default } + let(:config) do + { before_script: "cd ${PROJ_DIR} " } + end + + it { is_expected.to be_falsey } + end + + context 'when using the default job with script' do + let(:name) { :default } + let(:config) do + { + before_script: "cd ${PROJ_DIR} ", + script: "ls" + } + end + + it { is_expected.to be_truthy } + end + + context 'there are no shared keys between jobs and bridges' do + subject(:shared_values) do + described_class::ALLOWED_KEYS & Gitlab::Ci::Config::Entry::Bridge::ALLOWED_KEYS + end + + it { is_expected.to be_empty } + end end describe 'validations' do diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 5a2a374a39e..8292d6b27f4 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -2484,6 +2484,14 @@ module Gitlab end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs config should contain at least one visible job") end + it "returns errors if the job script is not defined" do + config = YAML.dump({ rspec: { before_script: "test" } }) + + expect do + Gitlab::Ci::YamlProcessor.new(config) + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec script can't be blank") + end + it "returns errors if there are no visible jobs defined" do config = YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => { script: 'ls' } }) expect do diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index 19d608cf48e..72d201eed77 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::IncomingEmail do end it 'returns true' do - expect(described_class.enabled?).to be_truthy + expect(described_class.enabled?).to be(true) end end @@ -20,7 +20,7 @@ RSpec.describe Gitlab::IncomingEmail do end it "returns false" do - expect(described_class.enabled?).to be_falsey + expect(described_class.enabled?).to be(false) end end end @@ -32,7 +32,7 @@ RSpec.describe Gitlab::IncomingEmail do end it 'confirms that wildcard is supported' do - expect(described_class.supports_wildcard?).to be_truthy + expect(described_class.supports_wildcard?).to be(true) end end @@ -42,7 +42,7 @@ RSpec.describe Gitlab::IncomingEmail do end it 'returns that wildcard is not supported' do - expect(described_class.supports_wildcard?).to be_falsey + expect(described_class.supports_wildcard?).to be(false) end end @@ -52,7 +52,7 @@ RSpec.describe Gitlab::IncomingEmail do end it 'returns that wildcard is not supported' do - expect(described_class.supports_wildcard?).to be_falsey + expect(described_class.supports_wildcard?).to be(false) end end end diff --git a/yarn.lock b/yarn.lock index 77e33c025c4..4b88e4f1eb7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2157,11 +2157,6 @@ babel-plugin-lodash@^3.3.4: lodash "^4.17.10" require-package-name "^2.0.1" -babel-plugin-rewire@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz#822562d72ed2c84e47c0f95ee232c920853e9d89" - integrity sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ== - babel-preset-jest@^24.6.0: version "24.6.0" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz#66f06136eefce87797539c0d63f1769cc3915984" |