Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d1cb802bac
commit
3ce55b46df
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1 +1 @@
|
|||
d758ff48fbf8392e08626b60685d037373347d72
|
||||
e3bedb3507c01fbe8395dd76589e095d7da14e66
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -5,6 +5,8 @@ mutation($input: JiraImportUsersInput!) {
|
|||
jiraDisplayName
|
||||
jiraEmail
|
||||
gitlabId
|
||||
gitlabName
|
||||
gitlabUsername
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
|
|
@ -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' });
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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 () => {};
|
||||
|
|
|
@ -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 () => {};
|
||||
|
|
|
@ -287,7 +287,7 @@ export default {
|
|||
</gl-tab>
|
||||
|
||||
<gl-tab
|
||||
:title="__('Versions')"
|
||||
:title="__('Other versions')"
|
||||
title-item-class="js-versions-tab"
|
||||
@click="getPackageVersions"
|
||||
>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 });
|
||||
},
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
export default ({
|
||||
fullReportEndpoint = '',
|
||||
summaryEndpoint = '',
|
||||
suiteEndpoint = '',
|
||||
useBuildSummaryReport = false,
|
||||
}) => ({
|
||||
summaryEndpoint,
|
||||
suiteEndpoint,
|
||||
fullReportEndpoint,
|
||||
testReports: {},
|
||||
selectedSuiteIndex: null,
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update versions tab to other versions
|
||||
merge_request: 37513
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show mapped user in Jira import form dropdown
|
||||
merge_request: 37575
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace fa-link icons with GitLab SVG link icon
|
||||
merge_request: 36973
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show relevant error messages when failing to match a CI job entry
|
||||
merge_request: 36536
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix CSV downloads for multiple series in the same chart
|
||||
merge_request: 37377
|
||||
author:
|
||||
type: fixed
|
|
@ -68,6 +68,7 @@ exceptions:
|
|||
- SSH
|
||||
- SSL
|
||||
- SSO
|
||||
- SVG
|
||||
- SVN
|
||||
- TCP
|
||||
- TIP
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
- Correct: `**{admin}** **Admin Area > Settings**` (**{admin}** **Admin Area > Settings**)
|
||||
- Incorrect: `**{admin}** **> Settings**` (**{admin}** **> Settings**)
|
||||
|
||||
This will ensure that the source Markdown remains readable and should help with accessibility.
|
||||
|
||||
The following are examples of source Markdown for menu items with their published output:
|
||||
For example, the following adds little to the accompanying text:
|
||||
|
||||
```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**
|
||||
```
|
||||
|
||||
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**
|
||||
|
||||
However, the following might help the reader connect the text to the user interface:
|
||||
|
||||
```markdown
|
||||
| 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. |
|
||||
```
|
||||
|
||||
| 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!
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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:**
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
module Bitbucket
|
||||
module Representation
|
||||
class Repo < Representation::Base
|
||||
attr_reader :owner, :slug
|
||||
|
||||
def initialize(raw)
|
||||
super(raw)
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,7 +16,7 @@ module Gitlab
|
|||
#
|
||||
class Entry
|
||||
attr_reader :entries
|
||||
attr_accessor :name
|
||||
attr_writer :name
|
||||
|
||||
def initialize(path, entries)
|
||||
@entries = entries
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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]}`;
|
||||
|
|
|
@ -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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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(),
|
||||
})),
|
||||
|
|
|
@ -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, {});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue