debian-mirror-gitlab/debian/patches/fix-mismatch-by-manual-merge.patch
2021-01-30 21:45:13 +05:30

2302 lines
65 KiB
Diff

Description: manual merge caused a mismatch
this patch fixes the mismatch
Author: Pirate Praveen <praveen@debian.org>
---
Last-Update: 2021-01-30
--- /dev/null
+++ gitlab-13.6.5/app/assets/javascripts/analytics/instance_statistics/components/pipelines_chart.vue
@@ -0,0 +1,215 @@
+<script>
+import { GlLineChart } from '@gitlab/ui/dist/charts';
+import { GlAlert } from '@gitlab/ui';
+import { mapKeys, mapValues, pick, some, sum } from 'lodash';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
+import { s__ } from '~/locale';
+import {
+ differenceInMonths,
+ formatDateAsMonth,
+ getDayDifference,
+} from '~/lib/utils/datetime_utility';
+import { getAverageByMonth, sortByDate, extractValues } from '../utils';
+import pipelineStatsQuery from '../graphql/queries/pipeline_stats.query.graphql';
+import { TODAY, START_DATE } from '../constants';
+
+const DATA_KEYS = [
+ 'pipelinesTotal',
+ 'pipelinesSucceeded',
+ 'pipelinesFailed',
+ 'pipelinesCanceled',
+ 'pipelinesSkipped',
+];
+const PREFIX = 'pipelines';
+
+export default {
+ name: 'PipelinesChart',
+ components: {
+ GlLineChart,
+ GlAlert,
+ ChartSkeletonLoader,
+ },
+ startDate: START_DATE,
+ endDate: TODAY,
+ i18n: {
+ loadPipelineChartError: s__(
+ 'InstanceAnalytics|Could not load the pipelines chart. Please refresh the page to try again.',
+ ),
+ noDataMessage: s__('InstanceAnalytics|There is no data available.'),
+ total: s__('InstanceAnalytics|Total'),
+ succeeded: s__('InstanceAnalytics|Succeeded'),
+ failed: s__('InstanceAnalytics|Failed'),
+ canceled: s__('InstanceAnalytics|Canceled'),
+ skipped: s__('InstanceAnalytics|Skipped'),
+ chartTitle: s__('InstanceAnalytics|Pipelines'),
+ yAxisTitle: s__('InstanceAnalytics|Items'),
+ xAxisTitle: s__('InstanceAnalytics|Month'),
+ },
+ data() {
+ return {
+ loading: true,
+ loadingError: null,
+ };
+ },
+ apollo: {
+ pipelineStats: {
+ query: pipelineStatsQuery,
+ variables() {
+ return {
+ firstTotal: this.totalDaysToShow,
+ firstSucceeded: this.totalDaysToShow,
+ firstFailed: this.totalDaysToShow,
+ firstCanceled: this.totalDaysToShow,
+ firstSkipped: this.totalDaysToShow,
+ };
+ },
+ update(data) {
+ const allData = extractValues(data, DATA_KEYS, PREFIX, 'nodes');
+ const allPageInfo = extractValues(data, DATA_KEYS, PREFIX, 'pageInfo');
+
+ return {
+ ...mapValues(allData, sortByDate),
+ ...allPageInfo,
+ };
+ },
+ result() {
+ if (this.hasNextPage) {
+ this.fetchNextPage();
+ }
+ },
+ error() {
+ this.handleError();
+ },
+ },
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.pipelineStats.loading;
+ },
+ totalDaysToShow() {
+ return getDayDifference(this.$options.startDate, this.$options.endDate);
+ },
+ firstVariables() {
+ const allData = pick(this.pipelineStats, [
+ 'nodesTotal',
+ 'nodesSucceeded',
+ 'nodesFailed',
+ 'nodesCanceled',
+ 'nodesSkipped',
+ ]);
+ const allDayDiffs = mapValues(allData, data => {
+ const firstdataPoint = data[0];
+ if (!firstdataPoint) {
+ return 0;
+ }
+
+ return Math.max(
+ 0,
+ getDayDifference(this.$options.startDate, new Date(firstdataPoint.recordedAt)),
+ );
+ });
+
+ return mapKeys(allDayDiffs, (value, key) => key.replace('nodes', 'first'));
+ },
+ cursorVariables() {
+ const pageInfoKeys = [
+ 'pageInfoTotal',
+ 'pageInfoSucceeded',
+ 'pageInfoFailed',
+ 'pageInfoCanceled',
+ 'pageInfoSkipped',
+ ];
+
+ return extractValues(this.pipelineStats, pageInfoKeys, 'pageInfo', 'endCursor');
+ },
+ hasNextPage() {
+ return (
+ sum(Object.values(this.firstVariables)) > 0 &&
+ some(this.pipelineStats, ({ hasNextPage }) => hasNextPage)
+ );
+ },
+ hasEmptyDataSet() {
+ return this.chartData.every(({ data }) => data.length === 0);
+ },
+ chartData() {
+ const allData = pick(this.pipelineStats, [
+ 'nodesTotal',
+ 'nodesSucceeded',
+ 'nodesFailed',
+ 'nodesCanceled',
+ 'nodesSkipped',
+ ]);
+ const options = { shouldRound: true };
+ return Object.keys(allData).map(key => {
+ const i18nName = key.slice('nodes'.length).toLowerCase();
+ return {
+ name: this.$options.i18n[i18nName],
+ data: getAverageByMonth(allData[key], options),
+ };
+ });
+ },
+ range() {
+ return {
+ min: this.$options.startDate,
+ max: this.$options.endDate,
+ };
+ },
+ chartOptions() {
+ const { endDate, startDate, i18n } = this.$options;
+ return {
+ xAxis: {
+ ...this.range,
+ name: i18n.xAxisTitle,
+ type: 'time',
+ splitNumber: differenceInMonths(startDate, endDate) + 1,
+ axisLabel: {
+ interval: 0,
+ showMinLabel: false,
+ showMaxLabel: false,
+ align: 'right',
+ formatter: formatDateAsMonth,
+ },
+ },
+ yAxis: {
+ name: i18n.yAxisTitle,
+ },
+ };
+ },
+ },
+ methods: {
+ handleError() {
+ this.loadingError = true;
+ },
+ fetchNextPage() {
+ this.$apollo.queries.pipelineStats
+ .fetchMore({
+ variables: {
+ ...this.firstVariables,
+ ...this.cursorVariables,
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) => {
+ return Object.keys(fetchMoreResult).reduce((memo, key) => {
+ const { nodes, ...rest } = fetchMoreResult[key];
+ const previousNodes = previousResult[key].nodes;
+ return { ...memo, [key]: { ...rest, nodes: [...previousNodes, ...nodes] } };
+ }, {});
+ },
+ })
+ .catch(this.handleError);
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <h3>{{ $options.i18n.chartTitle }}</h3>
+ <gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3">
+ {{ this.$options.i18n.loadPipelineChartError }}
+ </gl-alert>
+ <chart-skeleton-loader v-else-if="isLoading" />
+ <gl-alert v-else-if="hasEmptyDataSet" variant="info" :dismissible="false" class="gl-mt-3">
+ {{ $options.i18n.noDataMessage }}
+ </gl-alert>
+ <gl-line-chart v-else :option="chartOptions" :include-legend-avg-max="true" :data="chartData" />
+ </div>
+</template>
--- /dev/null
+++ gitlab-13.6.5/app/assets/javascripts/analytics/instance_statistics/graphql/queries/pipeline_stats.query.graphql
@@ -0,0 +1,76 @@
+#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "./count.fragment.graphql"
+
+query pipelineStats(
+ $firstTotal: Int
+ $firstSucceeded: Int
+ $firstFailed: Int
+ $firstCanceled: Int
+ $firstSkipped: Int
+ $endCursorTotal: String
+ $endCursorSucceeded: String
+ $endCursorFailed: String
+ $endCursorCanceled: String
+ $endCursorSkipped: String
+) {
+ pipelinesTotal: instanceStatisticsMeasurements(
+ identifier: PIPELINES
+ first: $firstTotal
+ after: $endCursorTotal
+ ) {
+ nodes {
+ ...Count
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ pipelinesSucceeded: instanceStatisticsMeasurements(
+ identifier: PIPELINES_SUCCEEDED
+ first: $firstSucceeded
+ after: $endCursorSucceeded
+ ) {
+ nodes {
+ ...Count
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ pipelinesFailed: instanceStatisticsMeasurements(
+ identifier: PIPELINES_FAILED
+ first: $firstFailed
+ after: $endCursorFailed
+ ) {
+ nodes {
+ ...Count
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ pipelinesCanceled: instanceStatisticsMeasurements(
+ identifier: PIPELINES_CANCELED
+ first: $firstCanceled
+ after: $endCursorCanceled
+ ) {
+ nodes {
+ ...Count
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ pipelinesSkipped: instanceStatisticsMeasurements(
+ identifier: PIPELINES_SKIPPED
+ first: $firstSkipped
+ after: $endCursorSkipped
+ ) {
+ nodes {
+ ...Count
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+}
--- /dev/null
+++ gitlab-13.6.5/app/assets/javascripts/performance_utils.js
@@ -0,0 +1,10 @@
+export const performanceMarkAndMeasure = ({ mark, measures = [] } = {}) => {
+ window.requestAnimationFrame(() => {
+ if (mark && !performance.getEntriesByName(mark).length) {
+ performance.mark(mark);
+ }
+ measures.forEach(measure => {
+ performance.measure(measure.name, measure.start, measure.end);
+ });
+ });
+};
--- /dev/null
+++ gitlab-13.6.5/app/assets/javascripts/search/dropdown_filter/components/dropdown_filter.vue
@@ -0,0 +1,100 @@
+<script>
+import { mapState } from 'vuex';
+import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
+import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
+import { sprintf, s__ } from '~/locale';
+
+export default {
+ name: 'DropdownFilter',
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownDivider,
+ },
+ props: {
+ filterData: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState(['query']),
+ scope() {
+ return this.query.scope;
+ },
+ supportedScopes() {
+ return Object.values(this.filterData.scopes);
+ },
+ initialFilter() {
+ return this.query[this.filterData.filterParam];
+ },
+ filter() {
+ return this.initialFilter || this.filterData.filters.ANY.value;
+ },
+ filtersArray() {
+ return this.filterData.filterByScope[this.scope];
+ },
+ selectedFilter: {
+ get() {
+ if (this.filtersArray.some(({ value }) => value === this.filter)) {
+ return this.filter;
+ }
+
+ return this.filterData.filters.ANY.value;
+ },
+ set(filter) {
+ visitUrl(setUrlParams({ [this.filterData.filterParam]: filter }));
+ },
+ },
+ selectedFilterText() {
+ const f = this.filtersArray.find(({ value }) => value === this.selectedFilter);
+ if (!f || f === this.filterData.filters.ANY) {
+ return sprintf(s__('Any %{header}'), { header: this.filterData.header });
+ }
+
+ return f.label;
+ },
+ showDropdown() {
+ return this.supportedScopes.includes(this.scope);
+ },
+ },
+ methods: {
+ dropDownItemClass(filter) {
+ return {
+ 'gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2! gl-mb-2':
+ filter === this.filterData.filters.ANY,
+ };
+ },
+ isFilterSelected(filter) {
+ return filter === this.selectedFilter;
+ },
+ handleFilterChange(filter) {
+ this.selectedFilter = filter;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-dropdown
+ v-if="showDropdown"
+ :text="selectedFilterText"
+ class="col-3 gl-pt-4 gl-pl-0 gl-pr-0 gl-mr-4"
+ menu-class="gl-w-full! gl-pl-0"
+ >
+ <header class="gl-text-center gl-font-weight-bold gl-font-lg">
+ {{ filterData.header }}
+ </header>
+ <gl-dropdown-divider />
+ <gl-dropdown-item
+ v-for="f in filtersArray"
+ :key="f.value"
+ :is-check-item="true"
+ :is-checked="isFilterSelected(f.value)"
+ :class="dropDownItemClass(f)"
+ @click="handleFilterChange(f.value)"
+ >
+ {{ f.label }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+</template>
--- /dev/null
+++ gitlab-13.6.5/app/assets/javascripts/search/dropdown_filter/constants/confidential_filter_data.js
@@ -0,0 +1,36 @@
+import { __ } from '~/locale';
+
+const header = __('Confidentiality');
+
+const filters = {
+ ANY: {
+ label: __('Any'),
+ value: null,
+ },
+ CONFIDENTIAL: {
+ label: __('Confidential'),
+ value: 'yes',
+ },
+ NOT_CONFIDENTIAL: {
+ label: __('Not confidential'),
+ value: 'no',
+ },
+};
+
+const scopes = {
+ ISSUES: 'issues',
+};
+
+const filterByScope = {
+ [scopes.ISSUES]: [filters.ANY, filters.CONFIDENTIAL, filters.NOT_CONFIDENTIAL],
+};
+
+const filterParam = 'confidential';
+
+export default {
+ header,
+ filters,
+ scopes,
+ filterByScope,
+ filterParam,
+};
--- /dev/null
+++ gitlab-13.6.5/app/assets/javascripts/search/dropdown_filter/constants/state_filter_data.js
@@ -0,0 +1,42 @@
+import { __ } from '~/locale';
+
+const header = __('Status');
+
+const filters = {
+ ANY: {
+ label: __('Any'),
+ value: 'all',
+ },
+ OPEN: {
+ label: __('Open'),
+ value: 'opened',
+ },
+ CLOSED: {
+ label: __('Closed'),
+ value: 'closed',
+ },
+ MERGED: {
+ label: __('Merged'),
+ value: 'merged',
+ },
+};
+
+const scopes = {
+ ISSUES: 'issues',
+ MERGE_REQUESTS: 'merge_requests',
+};
+
+const filterByScope = {
+ [scopes.ISSUES]: [filters.ANY, filters.OPEN, filters.CLOSED],
+ [scopes.MERGE_REQUESTS]: [filters.ANY, filters.OPEN, filters.MERGED, filters.CLOSED],
+};
+
+const filterParam = 'state';
+
+export default {
+ header,
+ filters,
+ scopes,
+ filterByScope,
+ filterParam,
+};
--- /dev/null
+++ gitlab-13.6.5/app/assets/javascripts/search/dropdown_filter/index.js
@@ -0,0 +1,38 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import DropdownFilter from './components/dropdown_filter.vue';
+import stateFilterData from './constants/state_filter_data';
+import confidentialFilterData from './constants/confidential_filter_data';
+
+Vue.use(Translate);
+
+const mountDropdownFilter = (store, { id, filterData }) => {
+ const el = document.getElementById(id);
+
+ if (!el) return false;
+
+ return new Vue({
+ el,
+ store,
+ render(createElement) {
+ return createElement(DropdownFilter, {
+ props: {
+ filterData,
+ },
+ });
+ },
+ });
+};
+
+const dropdownFilters = [
+ {
+ id: 'js-search-filter-by-state',
+ filterData: stateFilterData,
+ },
+ {
+ id: 'js-search-filter-by-confidential',
+ filterData: confidentialFilterData,
+ },
+];
+
+export default store => [...dropdownFilters].map(filter => mountDropdownFilter(store, filter));
--- /dev/null
+++ gitlab-13.6.5/app/assets/stylesheets/page_bundles/experimental_separate_sign_up.scss
@@ -0,0 +1,96 @@
+@import 'mixins_and_variables_and_functions';
+
+.signup-page {
+ .page-wrap {
+ background-color: var(--gray-10, $gray-10);
+ }
+
+ .signup-box-container {
+ max-width: 960px;
+ }
+
+ .signup-box {
+ background-color: var(--white, $white);
+ box-shadow: 0 0 0 1px var(--border-color, $border-color);
+ border-radius: $border-radius;
+ }
+
+ .form-control {
+ &:active,
+ &:focus {
+ background-color: var(--white, $white);
+ }
+ }
+
+ .devise-errors {
+ h2 {
+ font-size: $gl-font-size;
+ color: var(--red-700, $red-700);
+ }
+ }
+
+ .omniauth-divider {
+ &::before,
+ &::after {
+ content: '';
+ flex: 1;
+ border-bottom: 1px solid var(--gray-100, $gray-100);
+ margin: $gl-padding-24 0;
+ }
+
+ &::before {
+ margin-right: $gl-padding;
+ }
+
+ &::after {
+ margin-left: $gl-padding;
+ }
+ }
+
+ .omniauth-btn {
+ width: 48%;
+
+ @include media-breakpoint-down(md) {
+ width: 100%;
+ }
+
+ img {
+ width: $default-icon-size;
+ height: $default-icon-size;
+ }
+ }
+
+ .decline-page {
+ width: 350px;
+ }
+}
+
+.signup-page[data-page^='registrations:experience_levels'] {
+ $card-shadow-color: rgba(var(--black, $black), 0.2);
+
+ .page-wrap {
+ background-color: var(--white, $white);
+ }
+
+ .card-deck {
+ max-width: 828px;
+ }
+
+ .card {
+ transition: box-shadow 0.3s ease-in-out;
+ }
+
+ .card:hover {
+ box-shadow: 0 $gl-spacing-scale-3 $gl-spacing-scale-5 $card-shadow-color;
+ }
+
+ @media (min-width: $breakpoint-sm) {
+ .card-deck .card {
+ margin: 0 $gl-spacing-scale-3;
+ }
+ }
+
+ .stretched-link:hover {
+ text-decoration: none;
+ }
+}
--- /dev/null
+++ gitlab-13.6.5/app/models/ci/build_trace_chunks/legacy_fog.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module Ci
+ module BuildTraceChunks
+ class LegacyFog
+ def available?
+ object_store.enabled
+ end
+
+ def data(model)
+ connection.get_object(bucket_name, key(model))[:body]
+ rescue Excon::Error::NotFound
+ # If the object does not exist in the object storage, this method returns nil.
+ end
+
+ def set_data(model, new_data)
+ connection.put_object(bucket_name, key(model), new_data)
+ end
+
+ def append_data(model, new_data, offset)
+ if offset > 0
+ truncated_data = data(model).to_s.byteslice(0, offset)
+ new_data = truncated_data + new_data
+ end
+
+ set_data(model, new_data)
+ new_data.bytesize
+ end
+
+ def size(model)
+ data(model).to_s.bytesize
+ end
+
+ def delete_data(model)
+ delete_keys([[model.build_id, model.chunk_index]])
+ end
+
+ def keys(relation)
+ return [] unless available?
+
+ relation.pluck(:build_id, :chunk_index)
+ end
+
+ def delete_keys(keys)
+ keys.each do |key|
+ connection.delete_object(bucket_name, key_raw(*key))
+ end
+ end
+
+ private
+
+ def key(model)
+ key_raw(model.build_id, model.chunk_index)
+ end
+
+ def key_raw(build_id, chunk_index)
+ "tmp/builds/#{build_id.to_i}/chunks/#{chunk_index.to_i}.log"
+ end
+
+ def bucket_name
+ return unless available?
+
+ object_store.remote_directory
+ end
+
+ def connection
+ return unless available?
+
+ @connection ||= ::Fog::Storage.new(object_store.connection.to_hash.deep_symbolize_keys)
+ end
+
+ def object_store
+ Gitlab.config.artifacts.object_store
+ end
+ end
+ end
+end
--- /dev/null
+++ gitlab-13.6.5/app/views/search/results/_filters.html.haml
@@ -0,0 +1,7 @@
+.d-lg-flex.align-items-end
+ #js-search-filter-by-state{ 'v-cloak': true }
+ - if Feature.enabled?(:search_filter_by_confidential, @group)
+ #js-search-filter-by-confidential{ 'v-cloak': true }
+
+ - if %w(issues merge_requests).include?(@scope)
+ %hr.gl-mt-4.gl-mb-4
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/admin_approval_for_new_user_signups.yml
@@ -0,0 +1,7 @@
+---
+name: admin_approval_for_new_user_signups
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43827
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258980
+type: development
+group: group::access
+default_enabled: true
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/ci_always_refresh_merge_requests_from_beginning.yml
@@ -0,0 +1,7 @@
+---
+name: ci_always_refresh_merge_requests_from_beginning
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45232
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/268215
+type: development
+group: group::continuous integration
+default_enabled: false
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/ci_delete_objects_low_concurrency.yml
@@ -0,0 +1,7 @@
+---
+name: ci_delete_objects_low_concurrency
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39464
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247103
+group: group::continuous integration
+type: development
+default_enabled: false
\ No newline at end of file
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/ci_send_deployment_hook_when_start.yml
@@ -0,0 +1,7 @@
+---
+name: ci_send_deployment_hook_when_start
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41214
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247137
+group: group::progressive delivery
+type: development
+default_enabled: false
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/ci_trace_new_fog_store.yml
@@ -0,0 +1,7 @@
+---
+name: ci_trace_new_fog_store
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46209
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/273405
+type: development
+group: group::testing
+default_enabled: true
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/deploy_boards_dedupe_instances.yml
@@ -0,0 +1,7 @@
+---
+name: deploy_boards_dedupe_instances
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40768
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258214
+type: development
+group: group::progressive delivery
+default_enabled: false
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/deployment_filters.yml
@@ -0,0 +1,7 @@
+---
+name: deployment_filters
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44041
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267561
+type: development
+group: group::source code
+default_enabled: false
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/disable_shared_runners_on_group.yml
@@ -0,0 +1,7 @@
+---
+name: disable_shared_runners_on_group
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36080
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258991
+type: development
+group: group::runner
+default_enabled: true
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/expose_environment_path_in_alert_details.yml
@@ -0,0 +1,7 @@
+---
+name: expose_environment_path_in_alert_details
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43414
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258638
+type: development
+group: group::progressive delivery
+default_enabled: false
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/kubernetes_cluster_namespace_role_admin.yml
@@ -0,0 +1,7 @@
+---
+name: kubernetes_cluster_namespace_role_admin
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45479
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/270030
+type: development
+group: group::configure
+default_enabled: false
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/merge_base_pipelines.yml
@@ -0,0 +1,7 @@
+---
+name: merge_base_pipelines
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44648
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/263724
+type: development
+group: group::testing
+default_enabled: false
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/one_dimensional_matrix.yml
@@ -0,0 +1,7 @@
+---
+name: one_dimensional_matrix
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42170
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/256062
+type: development
+group: group::pipeline authoring
+default_enabled: true
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/search_filter_by_confidential.yml
@@ -0,0 +1,7 @@
+---
+name: search_filter_by_confidential
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40793
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/244923
+group: group::global search
+type: development
+default_enabled: false
\ No newline at end of file
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/soft_fail_count_by_state.yml
@@ -0,0 +1,7 @@
+---
+name: soft_fail_count_by_state
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44184
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/263222
+type: development
+group: group::source code
+default_enabled: false
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/sync_metrics_dashboards.yml
@@ -0,0 +1,7 @@
+---
+name: sync_metrics_dashboards
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39658
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/241793
+group: group::apm
+type: development
+default_enabled: false
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/development/track_unique_test_cases_parsed.yml
@@ -0,0 +1,7 @@
+---
+name: track_unique_test_cases_parsed
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41918
+rollout_issue_url:
+group: group::testing
+type: development
+default_enabled: false
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/licensed/incident_sla.yml
@@ -0,0 +1,7 @@
+---
+name: incident_sla
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43648
+rollout_issue_url:
+group: group::health
+type: licensed
+default_enabled: true
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/licensed/minimal_access_role.yml
@@ -0,0 +1,7 @@
+---
+name: minimal_access_role
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40942
+rollout_issue_url:
+group: group::access
+type: licensed
+default_enabled: true
--- /dev/null
+++ gitlab-13.6.5/config/feature_flags/licensed/resource_access_token.yml
@@ -0,0 +1,7 @@
+---
+name: resource_access_token
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29622
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235765
+group: group::access
+type: licensed
+default_enabled: true
--- /dev/null
+++ gitlab-13.6.5/doc/user/admin_area/analytics/instance_statistics.md
@@ -0,0 +1,18 @@
+# Instance Statistics
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/235754) in GitLab 13.4.
+
+Instance Statistics gives you an overview of how much data your instance contains, and how quickly this volume is changing over time.
+
+## Total counts
+
+At the top of the page, Instance Statistics shows total counts for:
+
+- Users
+- Projects
+- Groups
+- Issues
+- Merge Requests
+- Pipelines
+
+These figures can be useful for understanding how much data your instance contains in total.
--- /dev/null
+++ gitlab-13.6.5/lib/gitlab/bulk_import/client.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BulkImport
+ class Client
+ API_VERSION = 'v4'.freeze
+ DEFAULT_PAGE = 1.freeze
+ DEFAULT_PER_PAGE = 30.freeze
+
+ ConnectionError = Class.new(StandardError)
+
+ def initialize(uri:, token:, page: DEFAULT_PAGE, per_page: DEFAULT_PER_PAGE, api_version: API_VERSION)
+ @uri = URI.parse(uri)
+ @token = token&.strip
+ @page = page
+ @per_page = per_page
+ @api_version = api_version
+ end
+
+ def get(resource, query = {})
+ response = with_error_handling do
+ Gitlab::HTTP.get(
+ resource_url(resource),
+ headers: request_headers,
+ follow_redirects: false,
+ query: query.merge(request_query)
+ )
+ end
+
+ response.parsed_response
+ end
+
+ private
+
+ def request_query
+ {
+ page: @page,
+ per_page: @per_page
+ }
+ end
+
+ def request_headers
+ {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Bearer #{@token}"
+ }
+ end
+
+ def with_error_handling
+ response = yield
+
+ raise ConnectionError.new("Error #{response.code}") unless response.success?
+
+ response
+ rescue *Gitlab::HTTP::HTTP_ERRORS => e
+ raise ConnectionError, e
+ end
+
+ def base_uri
+ @base_uri ||= "#{@uri.scheme}://#{@uri.host}:#{@uri.port}"
+ end
+
+ def api_url
+ Gitlab::Utils.append_path(base_uri, "/api/#{@api_version}")
+ end
+
+ def resource_url(resource)
+ Gitlab::Utils.append_path(api_url, resource)
+ end
+ end
+ end
+end
--- /dev/null
+++ gitlab-13.6.5/lib/gitlab/import_export/project/sample/sample_data_relation_tree_restorer.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ImportExport
+ module Project
+ module Sample
+ class SampleDataRelationTreeRestorer < RelationTreeRestorer
+ DATE_MODELS = %i[issues milestones].freeze
+
+ def initialize(*args)
+ super
+
+ date_calculator
+ end
+
+ private
+
+ def build_relation(relation_key, relation_definition, data_hash)
+ # Override due date attributes in data hash for Sample Data templates
+ # Dates are moved by taking the closest one to average and moving that (and rest around it) to the date of import
+ # TODO: To move this logic to RelationFactory (see: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41699#note_430465333)
+ override_date_attributes!(relation_key, data_hash)
+ super
+ end
+
+ def override_date_attributes!(relation_key, data_hash)
+ return unless DATE_MODELS.include?(relation_key.to_sym)
+
+ data_hash['start_date'] = date_calculator.calculate_by_closest_date_to_average(data_hash['start_date'].to_time) unless data_hash['start_date'].nil?
+ data_hash['due_date'] = date_calculator.calculate_by_closest_date_to_average(data_hash['due_date'].to_time) unless data_hash['due_date'].nil?
+ end
+
+ # TODO: Move clear logic into main comsume_relation method (see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41699#note_430465330)
+ def dates
+ unless relation_reader.legacy?
+ DATE_MODELS.map do |tag|
+ relation_reader.consume_relation(@importable_path, tag).map { |model| model.first['due_date'] }.tap do
+ relation_reader.clear_consumed_relations
+ end
+ end
+ end
+ end
+
+ def date_calculator
+ @date_calculator ||= Gitlab::ImportExport::Project::Sample::DateCalculator.new(dates)
+ end
+ end
+ end
+ end
+ end
+end
--- /dev/null
+++ gitlab-13.6.5/lib/gitlab/middleware/handle_null_bytes.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Middleware
+ # There is no valid reason for a request to contain a null byte (U+0000)
+ # so just return HTTP 400 (Bad Request) if we receive one
+ class HandleNullBytes
+ NULL_BYTE_REGEX = Regexp.new(Regexp.escape("\u0000")).freeze
+
+ attr_reader :app
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ return [400, {}, ["Bad Request"]] if request_has_null_byte?(env)
+
+ app.call(env)
+ end
+
+ private
+
+ def request_has_null_byte?(request)
+ return false if ENV['REJECT_NULL_BYTES'] == "1"
+
+ request = Rack::Request.new(request)
+
+ request.params.values.any? do |value|
+ param_has_null_byte?(value)
+ end
+ end
+
+ def param_has_null_byte?(value, depth = 0)
+ # Guard against possible attack sending large amounts of nested params
+ # Should be safe as deeply nested params are highly uncommon.
+ return false if depth > 2
+
+ depth += 1
+
+ if value.respond_to?(:match)
+ string_contains_null_byte?(value)
+ elsif value.respond_to?(:values)
+ value.values.any? do |hash_value|
+ param_has_null_byte?(hash_value, depth)
+ end
+ elsif value.is_a?(Array)
+ value.any? do |array_value|
+ param_has_null_byte?(array_value, depth)
+ end
+ else
+ false
+ end
+ end
+
+ def string_contains_null_byte?(string)
+ string.match?(NULL_BYTE_REGEX)
+ end
+ end
+ end
+end
--- /dev/null
+++ gitlab-13.6.5/spec/fixtures/packages/debian/libsample0_1.2.3~alpha2-1_amd64.deb
@@ -0,0 +1 @@
+empty
--- /dev/null
+++ gitlab-13.6.5/spec/frontend/alert_settings/alerts_integrations_list_spec.js
@@ -0,0 +1,89 @@
+import { GlTable, GlIcon } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import Tracking from '~/tracking';
+import AlertIntegrationsList, {
+ i18n,
+} from '~/alerts_settings/components/alerts_integrations_list.vue';
+import { trackAlertIntergrationsViewsOptions } from '~/alerts_settings/constants';
+
+const mockIntegrations = [
+ {
+ activated: true,
+ name: 'Integration 1',
+ type: 'HTTP endpoint',
+ },
+ {
+ activated: false,
+ name: 'Integration 2',
+ type: 'HTTP endpoint',
+ },
+];
+
+describe('AlertIntegrationsList', () => {
+ let wrapper;
+
+ function mountComponent(propsData = {}) {
+ wrapper = mount(AlertIntegrationsList, {
+ propsData: {
+ integrations: mockIntegrations,
+ ...propsData,
+ },
+ stubs: {
+ GlIcon: true,
+ },
+ });
+ }
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ const findTableComponent = () => wrapper.find(GlTable);
+ const finsStatusCell = () => wrapper.findAll('[data-testid="integration-activated-status"]');
+
+ it('renders a table', () => {
+ expect(findTableComponent().exists()).toBe(true);
+ });
+
+ it('renders an empty state when no integrations provided', () => {
+ mountComponent({ integrations: [] });
+ expect(findTableComponent().text()).toContain(i18n.emptyState);
+ });
+
+ describe('integration status', () => {
+ it('enabled', () => {
+ const cell = finsStatusCell().at(0);
+ const activatedIcon = cell.find(GlIcon);
+ expect(cell.text()).toBe(i18n.status.enabled.name);
+ expect(activatedIcon.attributes('name')).toBe('check-circle-filled');
+ expect(activatedIcon.attributes('title')).toBe(i18n.status.enabled.tooltip);
+ });
+
+ it('disabled', () => {
+ const cell = finsStatusCell().at(1);
+ const notActivatedIcon = cell.find(GlIcon);
+ expect(cell.text()).toBe(i18n.status.disabled.name);
+ expect(notActivatedIcon.attributes('name')).toBe('warning-solid');
+ expect(notActivatedIcon.attributes('title')).toBe(i18n.status.disabled.tooltip);
+ });
+ });
+
+ describe('Snowplow tracking', () => {
+ beforeEach(() => {
+ jest.spyOn(Tracking, 'event');
+ mountComponent();
+ });
+
+ it('should track alert list page views', () => {
+ const { category, action } = trackAlertIntergrationsViewsOptions;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
+ });
+ });
+});
--- /dev/null
+++ gitlab-13.6.5/spec/frontend/analytics/instance_statistics/components/__snapshots__/pipelines_chart_spec.js.snap
@@ -0,0 +1,161 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PipelinesChart when fetching more data when the fetchMore query returns data passes the data to the line chart 1`] = `
+Array [
+ Object {
+ "data": Array [
+ Array [
+ "2020-06-01",
+ 21,
+ ],
+ Array [
+ "2020-07-01",
+ 10,
+ ],
+ Array [
+ "2020-08-01",
+ 5,
+ ],
+ ],
+ "name": "Total",
+ },
+ Object {
+ "data": Array [
+ Array [
+ "2020-06-01",
+ 21,
+ ],
+ Array [
+ "2020-07-01",
+ 10,
+ ],
+ Array [
+ "2020-08-01",
+ 5,
+ ],
+ ],
+ "name": "Succeeded",
+ },
+ Object {
+ "data": Array [
+ Array [
+ "2020-06-01",
+ 22,
+ ],
+ Array [
+ "2020-07-01",
+ 41,
+ ],
+ Array [
+ "2020-08-01",
+ 5,
+ ],
+ ],
+ "name": "Failed",
+ },
+ Object {
+ "data": Array [
+ Array [
+ "2020-06-01",
+ 21,
+ ],
+ Array [
+ "2020-07-01",
+ 10,
+ ],
+ Array [
+ "2020-08-01",
+ 5,
+ ],
+ ],
+ "name": "Canceled",
+ },
+ Object {
+ "data": Array [
+ Array [
+ "2020-06-01",
+ 21,
+ ],
+ Array [
+ "2020-07-01",
+ 10,
+ ],
+ Array [
+ "2020-08-01",
+ 5,
+ ],
+ ],
+ "name": "Skipped",
+ },
+]
+`;
+
+exports[`PipelinesChart with data passes the data to the line chart 1`] = `
+Array [
+ Object {
+ "data": Array [
+ Array [
+ "2020-06-01",
+ 22,
+ ],
+ Array [
+ "2020-07-01",
+ 41,
+ ],
+ ],
+ "name": "Total",
+ },
+ Object {
+ "data": Array [
+ Array [
+ "2020-06-01",
+ 21,
+ ],
+ Array [
+ "2020-07-01",
+ 10,
+ ],
+ ],
+ "name": "Succeeded",
+ },
+ Object {
+ "data": Array [
+ Array [
+ "2020-06-01",
+ 21,
+ ],
+ Array [
+ "2020-07-01",
+ 10,
+ ],
+ ],
+ "name": "Failed",
+ },
+ Object {
+ "data": Array [
+ Array [
+ "2020-06-01",
+ 22,
+ ],
+ Array [
+ "2020-07-01",
+ 41,
+ ],
+ ],
+ "name": "Canceled",
+ },
+ Object {
+ "data": Array [
+ Array [
+ "2020-06-01",
+ 22,
+ ],
+ Array [
+ "2020-07-01",
+ 41,
+ ],
+ ],
+ "name": "Skipped",
+ },
+]
+`;
--- /dev/null
+++ gitlab-13.6.5/spec/frontend/analytics/instance_statistics/components/pipelines_chart_spec.js
@@ -0,0 +1,189 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlLineChart } from '@gitlab/ui/dist/charts';
+import { GlAlert } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
+import PipelinesChart from '~/analytics/instance_statistics/components/pipelines_chart.vue';
+import pipelinesStatsQuery from '~/analytics/instance_statistics/graphql/queries/pipeline_stats.query.graphql';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
+import { mockCountsData1, mockCountsData2 } from '../mock_data';
+import { getApolloResponse } from '../apollo_mock_data';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('PipelinesChart', () => {
+ let wrapper;
+ let queryHandler;
+
+ const createApolloProvider = pipelineStatsHandler => {
+ return createMockApollo([[pipelinesStatsQuery, pipelineStatsHandler]]);
+ };
+
+ const createComponent = apolloProvider => {
+ return shallowMount(PipelinesChart, {
+ localVue,
+ apolloProvider,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findLoader = () => wrapper.find(ChartSkeletonLoader);
+ const findChart = () => wrapper.find(GlLineChart);
+ const findAlert = () => wrapper.find(GlAlert);
+
+ describe('while loading', () => {
+ beforeEach(() => {
+ queryHandler = jest.fn().mockReturnValue(new Promise(() => {}));
+ const apolloProvider = createApolloProvider(queryHandler);
+ wrapper = createComponent(apolloProvider);
+ });
+
+ it('requests data', () => {
+ expect(queryHandler).toBeCalledTimes(1);
+ });
+
+ it('displays the skeleton loader', () => {
+ expect(findLoader().exists()).toBe(true);
+ });
+
+ it('hides the chart', () => {
+ expect(findChart().exists()).toBe(false);
+ });
+
+ it('does not show an error', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ describe('without data', () => {
+ beforeEach(() => {
+ const emptyResponse = getApolloResponse();
+ queryHandler = jest.fn().mockResolvedValue(emptyResponse);
+ const apolloProvider = createApolloProvider(queryHandler);
+ wrapper = createComponent(apolloProvider);
+ });
+
+ it('renders an no data message', () => {
+ expect(findAlert().text()).toBe('There is no data available.');
+ });
+
+ it('hides the skeleton loader', () => {
+ expect(findLoader().exists()).toBe(false);
+ });
+
+ it('renders the chart', () => {
+ expect(findChart().exists()).toBe(false);
+ });
+ });
+
+ describe('with data', () => {
+ beforeEach(() => {
+ const response = getApolloResponse({
+ pipelinesTotal: mockCountsData1,
+ pipelinesSucceeded: mockCountsData2,
+ pipelinesFailed: mockCountsData2,
+ pipelinesCanceled: mockCountsData1,
+ pipelinesSkipped: mockCountsData1,
+ });
+ queryHandler = jest.fn().mockResolvedValue(response);
+ const apolloProvider = createApolloProvider(queryHandler);
+ wrapper = createComponent(apolloProvider);
+ });
+
+ it('requests data', () => {
+ expect(queryHandler).toBeCalledTimes(1);
+ });
+
+ it('hides the skeleton loader', () => {
+ expect(findLoader().exists()).toBe(false);
+ });
+
+ it('renders the chart', () => {
+ expect(findChart().exists()).toBe(true);
+ });
+
+ it('passes the data to the line chart', () => {
+ expect(findChart().props('data')).toMatchSnapshot();
+ });
+
+ it('does not show an error', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ describe('when fetching more data', () => {
+ const recordedAt = '2020-08-01';
+ describe('when the fetchMore query returns data', () => {
+ beforeEach(async () => {
+ const newData = { recordedAt, count: 5 };
+ const firstResponse = getApolloResponse({
+ pipelinesTotal: mockCountsData2,
+ pipelinesSucceeded: mockCountsData2,
+ pipelinesFailed: mockCountsData1,
+ pipelinesCanceled: mockCountsData2,
+ pipelinesSkipped: mockCountsData2,
+ hasNextPage: true,
+ });
+ const secondResponse = getApolloResponse({
+ pipelinesTotal: [newData],
+ pipelinesSucceeded: [newData],
+ pipelinesFailed: [newData],
+ pipelinesCanceled: [newData],
+ pipelinesSkipped: [newData],
+ hasNextPage: false,
+ });
+ queryHandler = jest
+ .fn()
+ .mockResolvedValueOnce(firstResponse)
+ .mockResolvedValueOnce(secondResponse);
+ const apolloProvider = createApolloProvider(queryHandler);
+ wrapper = createComponent(apolloProvider);
+
+ await wrapper.vm.$nextTick();
+ });
+
+ it('requests data twice', () => {
+ expect(queryHandler).toBeCalledTimes(2);
+ });
+
+ it('passes the data to the line chart', () => {
+ expect(findChart().props('data')).toMatchSnapshot();
+ });
+ });
+
+ describe('when the fetchMore query throws an error', () => {
+ beforeEach(async () => {
+ const response = getApolloResponse({
+ pipelinesTotal: mockCountsData2,
+ pipelinesSucceeded: mockCountsData2,
+ pipelinesFailed: mockCountsData1,
+ pipelinesCanceled: mockCountsData2,
+ pipelinesSkipped: mockCountsData2,
+ hasNextPage: true,
+ });
+ queryHandler = jest.fn().mockResolvedValue(response);
+ const apolloProvider = createApolloProvider(queryHandler);
+ wrapper = createComponent(apolloProvider);
+ jest
+ .spyOn(wrapper.vm.$apollo.queries.pipelineStats, 'fetchMore')
+ .mockImplementation(jest.fn().mockRejectedValue());
+ await wrapper.vm.$nextTick();
+ });
+
+ it('calls fetchMore', () => {
+ expect(wrapper.vm.$apollo.queries.pipelineStats.fetchMore).toHaveBeenCalledTimes(1);
+ });
+
+ it('show an error message', () => {
+ expect(findAlert().text()).toBe(
+ 'Could not load the pipelines chart. Please refresh the page to try again.',
+ );
+ });
+ });
+ });
+});
--- /dev/null
+++ gitlab-13.6.5/spec/frontend/search/dropdown_filter/components/dropdown_filter_spec.js
@@ -0,0 +1,196 @@
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import * as urlUtils from '~/lib/utils/url_utility';
+import initStore from '~/search/store';
+import DropdownFilter from '~/search/dropdown_filter/components/dropdown_filter.vue';
+import stateFilterData from '~/search/dropdown_filter/constants/state_filter_data';
+import confidentialFilterData from '~/search/dropdown_filter/constants/confidential_filter_data';
+import { MOCK_QUERY } from '../mock_data';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn(),
+ setUrlParams: jest.fn(),
+}));
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('DropdownFilter', () => {
+ let wrapper;
+ let store;
+
+ const createStore = options => {
+ store = initStore({ query: MOCK_QUERY, ...options });
+ };
+
+ const createComponent = (props = { filterData: stateFilterData }) => {
+ wrapper = shallowMount(DropdownFilter, {
+ localVue,
+ store,
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ store = null;
+ });
+
+ const findGlDropdown = () => wrapper.find(GlDropdown);
+ const findGlDropdownItems = () => findGlDropdown().findAll(GlDropdownItem);
+ const findDropdownItemsText = () => findGlDropdownItems().wrappers.map(w => w.text());
+ const firstDropDownItem = () => findGlDropdownItems().at(0);
+
+ describe('StatusFilter', () => {
+ describe('template', () => {
+ describe.each`
+ scope | showDropdown
+ ${'issues'} | ${true}
+ ${'merge_requests'} | ${true}
+ ${'projects'} | ${false}
+ ${'milestones'} | ${false}
+ ${'users'} | ${false}
+ ${'notes'} | ${false}
+ ${'wiki_blobs'} | ${false}
+ ${'blobs'} | ${false}
+ `(`dropdown`, ({ scope, showDropdown }) => {
+ beforeEach(() => {
+ createStore({ query: { ...MOCK_QUERY, scope } });
+ createComponent();
+ });
+
+ it(`does${showDropdown ? '' : ' not'} render when scope is ${scope}`, () => {
+ expect(findGlDropdown().exists()).toBe(showDropdown);
+ });
+ });
+
+ describe.each`
+ initialFilter | label
+ ${stateFilterData.filters.ANY.value} | ${`Any ${stateFilterData.header}`}
+ ${stateFilterData.filters.OPEN.value} | ${stateFilterData.filters.OPEN.label}
+ ${stateFilterData.filters.CLOSED.value} | ${stateFilterData.filters.CLOSED.label}
+ `(`filter text`, ({ initialFilter, label }) => {
+ describe(`when initialFilter is ${initialFilter}`, () => {
+ beforeEach(() => {
+ createStore({ query: { ...MOCK_QUERY, [stateFilterData.filterParam]: initialFilter } });
+ createComponent();
+ });
+
+ it(`sets dropdown label to ${label}`, () => {
+ expect(findGlDropdown().attributes('text')).toBe(label);
+ });
+ });
+ });
+ });
+
+ describe('Filter options', () => {
+ beforeEach(() => {
+ createStore();
+ createComponent();
+ });
+
+ it('renders a dropdown item for each filterOption', () => {
+ expect(findDropdownItemsText()).toStrictEqual(
+ stateFilterData.filterByScope[stateFilterData.scopes.ISSUES].map(v => {
+ return v.label;
+ }),
+ );
+ });
+
+ it('clicking a dropdown item calls setUrlParams', () => {
+ const filter = stateFilterData.filters[Object.keys(stateFilterData.filters)[0]].value;
+ firstDropDownItem().vm.$emit('click');
+
+ expect(urlUtils.setUrlParams).toHaveBeenCalledWith({
+ [stateFilterData.filterParam]: filter,
+ });
+ });
+
+ it('clicking a dropdown item calls visitUrl', () => {
+ firstDropDownItem().vm.$emit('click');
+
+ expect(urlUtils.visitUrl).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('ConfidentialFilter', () => {
+ describe('template', () => {
+ describe.each`
+ scope | showDropdown
+ ${'issues'} | ${true}
+ ${'merge_requests'} | ${false}
+ ${'projects'} | ${false}
+ ${'milestones'} | ${false}
+ ${'users'} | ${false}
+ ${'notes'} | ${false}
+ ${'wiki_blobs'} | ${false}
+ ${'blobs'} | ${false}
+ `(`dropdown`, ({ scope, showDropdown }) => {
+ beforeEach(() => {
+ createStore({ query: { ...MOCK_QUERY, scope } });
+ createComponent({ filterData: confidentialFilterData });
+ });
+
+ it(`does${showDropdown ? '' : ' not'} render when scope is ${scope}`, () => {
+ expect(findGlDropdown().exists()).toBe(showDropdown);
+ });
+ });
+
+ describe.each`
+ initialFilter | label
+ ${confidentialFilterData.filters.ANY.value} | ${`Any ${confidentialFilterData.header}`}
+ ${confidentialFilterData.filters.CONFIDENTIAL.value} | ${confidentialFilterData.filters.CONFIDENTIAL.label}
+ ${confidentialFilterData.filters.NOT_CONFIDENTIAL.value} | ${confidentialFilterData.filters.NOT_CONFIDENTIAL.label}
+ `(`filter text`, ({ initialFilter, label }) => {
+ describe(`when initialFilter is ${initialFilter}`, () => {
+ beforeEach(() => {
+ createStore({
+ query: { ...MOCK_QUERY, [confidentialFilterData.filterParam]: initialFilter },
+ });
+ createComponent({ filterData: confidentialFilterData });
+ });
+
+ it(`sets dropdown label to ${label}`, () => {
+ expect(findGlDropdown().attributes('text')).toBe(label);
+ });
+ });
+ });
+ });
+ });
+
+ describe('Filter options', () => {
+ beforeEach(() => {
+ createStore();
+ createComponent({ filterData: confidentialFilterData });
+ });
+
+ it('renders a dropdown item for each filterOption', () => {
+ expect(findDropdownItemsText()).toStrictEqual(
+ confidentialFilterData.filterByScope[confidentialFilterData.scopes.ISSUES].map(v => {
+ return v.label;
+ }),
+ );
+ });
+
+ it('clicking a dropdown item calls setUrlParams', () => {
+ const filter =
+ confidentialFilterData.filters[Object.keys(confidentialFilterData.filters)[0]].value;
+ firstDropDownItem().vm.$emit('click');
+
+ expect(urlUtils.setUrlParams).toHaveBeenCalledWith({
+ [confidentialFilterData.filterParam]: filter,
+ });
+ });
+
+ it('clicking a dropdown item calls visitUrl', () => {
+ firstDropDownItem().vm.$emit('click');
+
+ expect(urlUtils.visitUrl).toHaveBeenCalled();
+ });
+ });
+});
--- /dev/null
+++ gitlab-13.6.5/spec/frontend/search/dropdown_filter/mock_data.js
@@ -0,0 +1,5 @@
+export const MOCK_QUERY = {
+ scope: 'issues',
+ state: 'all',
+ confidential: null,
+};
--- /dev/null
+++ gitlab-13.6.5/spec/lib/gitlab/bulk_import/client_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BulkImport::Client do
+ include ImportSpecHelper
+
+ let(:uri) { 'http://gitlab.example' }
+ let(:token) { 'token' }
+ let(:resource) { 'resource' }
+
+ subject { described_class.new(uri: uri, token: token) }
+
+ describe '#get' do
+ let(:response_double) { double(code: 200, success?: true, parsed_response: {}) }
+
+ shared_examples 'performs network request' do
+ it 'performs network request' do
+ expect(Gitlab::HTTP).to receive(:get).with(*expected_args).and_return(response_double)
+
+ subject.get(resource)
+ end
+ end
+
+ describe 'parsed response' do
+ it 'returns parsed response' do
+ response_double = double(code: 200, success?: true, parsed_response: [{ id: 1 }, { id: 2 }])
+
+ allow(Gitlab::HTTP).to receive(:get).and_return(response_double)
+
+ expect(subject.get(resource)).to eq(response_double.parsed_response)
+ end
+ end
+
+ describe 'request query' do
+ include_examples 'performs network request' do
+ let(:expected_args) do
+ [
+ anything,
+ hash_including(
+ query: {
+ page: described_class::DEFAULT_PAGE,
+ per_page: described_class::DEFAULT_PER_PAGE
+ }
+ )
+ ]
+ end
+ end
+ end
+
+ describe 'request headers' do
+ include_examples 'performs network request' do
+ let(:expected_args) do
+ [
+ anything,
+ hash_including(
+ headers: {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Bearer #{token}"
+ }
+ )
+ ]
+ end
+ end
+ end
+
+ describe 'request uri' do
+ include_examples 'performs network request' do
+ let(:expected_args) do
+ ['http://gitlab.example:80/api/v4/resource', anything]
+ end
+ end
+ end
+
+ context 'error handling' do
+ context 'when error occurred' do
+ it 'raises ConnectionError' do
+ allow(Gitlab::HTTP).to receive(:get).and_raise(Errno::ECONNREFUSED)
+
+ expect { subject.get(resource) }.to raise_exception(described_class::ConnectionError)
+ end
+ end
+
+ context 'when response is not success' do
+ it 'raises ConnectionError' do
+ response_double = double(code: 503, success?: false)
+
+ allow(Gitlab::HTTP).to receive(:get).and_return(response_double)
+
+ expect { subject.get(resource) }.to raise_exception(described_class::ConnectionError)
+ end
+ end
+ end
+ end
+end
--- /dev/null
+++ gitlab-13.6.5/spec/lib/gitlab/import_export/project/sample/sample_data_relation_tree_restorer_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+# This spec is a lightweight version of:
+# * project/tree_restorer_spec.rb
+#
+# In depth testing is being done in the above specs.
+# This spec tests that restore of the sample project works
+# but does not have 100% relation coverage.
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ImportExport::Project::Sample::SampleDataRelationTreeRestorer do
+ include_context 'relation tree restorer shared context'
+
+ let(:sample_data_relation_tree_restorer) do
+ described_class.new(
+ user: user,
+ shared: shared,
+ relation_reader: relation_reader,
+ object_builder: object_builder,
+ members_mapper: members_mapper,
+ relation_factory: relation_factory,
+ reader: reader,
+ importable: importable,
+ importable_path: importable_path,
+ importable_attributes: attributes
+ )
+ end
+
+ subject { sample_data_relation_tree_restorer.restore }
+
+ shared_examples 'import project successfully' do
+ it 'restores project tree' do
+ expect(subject).to eq(true)
+ end
+
+ describe 'imported project' do
+ let(:project) { Project.find_by_path('project') }
+
+ before do
+ subject
+ end
+
+ it 'has the project attributes and relations', :aggregate_failures do
+ expect(project.description).to eq('Nisi et repellendus ut enim quo accusamus vel magnam.')
+ expect(project.issues.count).to eq(10)
+ expect(project.milestones.count).to eq(3)
+ expect(project.labels.count).to eq(2)
+ expect(project.project_feature).not_to be_nil
+ end
+
+ it 'has issues with correctly updated due dates' do
+ due_dates = due_dates(project.issues)
+
+ expect(due_dates).to match_array([Date.today - 7.days, Date.today, Date.today + 7.days])
+ end
+
+ it 'has milestones with correctly updated due dates' do
+ due_dates = due_dates(project.milestones)
+
+ expect(due_dates).to match_array([Date.today - 7.days, Date.today, Date.today + 7.days])
+ end
+
+ def due_dates(relations)
+ due_dates = relations.map { |relation| relation['due_date'] }
+ due_dates.compact!
+ due_dates.sort
+ end
+ end
+ end
+
+ context 'when restoring a project' do
+ let(:importable) { create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') }
+ let(:importable_name) { 'project' }
+ let(:importable_path) { 'project' }
+ let(:object_builder) { Gitlab::ImportExport::Project::ObjectBuilder }
+ let(:relation_factory) { Gitlab::ImportExport::Project::RelationFactory }
+ let(:reader) { Gitlab::ImportExport::Reader.new(shared: shared) }
+
+ context 'using ndjson reader' do
+ let(:path) { 'spec/fixtures/lib/gitlab/import_export/sample_data/tree' }
+ let(:relation_reader) { Gitlab::ImportExport::JSON::NdjsonReader.new(path) }
+
+ it_behaves_like 'import project successfully'
+ end
+ end
+end
--- /dev/null
+++ gitlab-13.6.5/spec/lib/gitlab/middleware/handle_null_bytes_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require "rack/test"
+
+RSpec.describe Gitlab::Middleware::HandleNullBytes do
+ let(:null_byte) { "\u0000" }
+ let(:error_400) { [400, {}, ["Bad Request"]] }
+ let(:app) { double(:app) }
+
+ subject { described_class.new(app) }
+
+ before do
+ allow(app).to receive(:call) do |args|
+ args
+ end
+ end
+
+ def env_for(params = {})
+ Rack::MockRequest.env_for('/', { params: params })
+ end
+
+ context 'with null bytes in params' do
+ it 'rejects null bytes in a top level param' do
+ env = env_for(name: "null#{null_byte}byte")
+
+ expect(subject.call(env)).to eq error_400
+ end
+
+ it "responds with 400 BadRequest for hashes with strings" do
+ env = env_for(name: { inner_key: "I am #{null_byte} bad" })
+
+ expect(subject.call(env)).to eq error_400
+ end
+
+ it "responds with 400 BadRequest for arrays with strings" do
+ env = env_for(name: ["I am #{null_byte} bad"])
+
+ expect(subject.call(env)).to eq error_400
+ end
+
+ it "responds with 400 BadRequest for arrays containing hashes with string values" do
+ env = env_for(name: [
+ {
+ inner_key: "I am #{null_byte} bad"
+ }
+ ])
+
+ expect(subject.call(env)).to eq error_400
+ end
+
+ it "gives up and does not 400 with too deeply nested params" do
+ env = env_for(name: [
+ {
+ inner_key: { deeper_key: [{ hash_inside_array_key: "I am #{null_byte} bad" }] }
+ }
+ ])
+
+ expect(subject.call(env)).not_to eq error_400
+ end
+ end
+
+ context 'without null bytes in params' do
+ it "does not respond with a 400 for strings" do
+ env = env_for(name: "safe name")
+
+ expect(subject.call(env)).not_to eq error_400
+ end
+
+ it "does not respond with a 400 with no params" do
+ env = env_for
+
+ expect(subject.call(env)).not_to eq error_400
+ end
+ end
+
+ context 'when disabled via env flag' do
+ before do
+ stub_env('REJECT_NULL_BYTES', '1')
+ end
+
+ it 'does not respond with a 400 no matter what' do
+ env = env_for(name: "null#{null_byte}byte")
+
+ expect(subject.call(env)).not_to eq error_400
+ end
+ end
+end
--- /dev/null
+++ gitlab-13.6.5/spec/migrations/schedule_blocked_by_links_replacement_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20201015073808_schedule_blocked_by_links_replacement')
+
+RSpec.describe ScheduleBlockedByLinksReplacement do
+ let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { table(:projects).create!(namespace_id: namespace.id, name: 'gitlab') }
+ let(:issue1) { table(:issues).create!(project_id: project.id, title: 'a') }
+ let(:issue2) { table(:issues).create!(project_id: project.id, title: 'b') }
+ let(:issue3) { table(:issues).create!(project_id: project.id, title: 'c') }
+ let!(:issue_links) do
+ [
+ table(:issue_links).create!(source_id: issue1.id, target_id: issue2.id, link_type: 1),
+ table(:issue_links).create!(source_id: issue2.id, target_id: issue1.id, link_type: 2),
+ table(:issue_links).create!(source_id: issue1.id, target_id: issue3.id, link_type: 2)
+ ]
+ end
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+ end
+
+ it 'schedules jobs for blocked_by links' do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 2.minutes, issue_links[1].id, issue_links[1].id)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 4.minutes, issue_links[2].id, issue_links[2].id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
--- /dev/null
+++ gitlab-13.6.5/spec/models/ci/build_trace_chunks/legacy_fog_spec.rb
@@ -0,0 +1,164 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::BuildTraceChunks::LegacyFog do
+ let(:data_store) { described_class.new }
+
+ before do
+ stub_artifacts_object_storage
+ end
+
+ describe '#available?' do
+ subject { data_store.available? }
+
+ context 'when object storage is enabled' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when object storage is disabled' do
+ before do
+ stub_artifacts_object_storage(enabled: false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#data' do
+ subject { data_store.data(model) }
+
+ context 'when data exists' do
+ let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
+
+ it 'returns the data' do
+ is_expected.to eq('sample data in fog')
+ end
+ end
+
+ context 'when data does not exist' do
+ let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
+
+ it 'returns nil' do
+ expect(data_store.data(model)).to be_nil
+ end
+ end
+ end
+
+ describe '#set_data' do
+ let(:new_data) { 'abc123' }
+
+ context 'when data exists' do
+ let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
+
+ it 'overwrites data' do
+ expect(data_store.data(model)).to eq('sample data in fog')
+
+ data_store.set_data(model, new_data)
+
+ expect(data_store.data(model)).to eq new_data
+ end
+ end
+
+ context 'when data does not exist' do
+ let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
+
+ it 'sets new data' do
+ expect(data_store.data(model)).to be_nil
+
+ data_store.set_data(model, new_data)
+
+ expect(data_store.data(model)).to eq new_data
+ end
+ end
+ end
+
+ describe '#delete_data' do
+ subject { data_store.delete_data(model) }
+
+ context 'when data exists' do
+ let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
+
+ it 'deletes data' do
+ expect(data_store.data(model)).to eq('sample data in fog')
+
+ subject
+
+ expect(data_store.data(model)).to be_nil
+ end
+ end
+
+ context 'when data does not exist' do
+ let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
+
+ it 'does nothing' do
+ expect(data_store.data(model)).to be_nil
+
+ subject
+
+ expect(data_store.data(model)).to be_nil
+ end
+ end
+ end
+
+ describe '#size' do
+ context 'when data exists' do
+ let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'üabcd') }
+
+ it 'returns data bytesize correctly' do
+ expect(data_store.size(model)).to eq 6
+ end
+ end
+
+ context 'when data does not exist' do
+ let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
+
+ it 'returns zero' do
+ expect(data_store.size(model)).to be_zero
+ end
+ end
+ end
+
+ describe '#keys' do
+ subject { data_store.keys(relation) }
+
+ let(:build) { create(:ci_build) }
+ let(:relation) { build.trace_chunks }
+
+ before do
+ create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 0, build: build)
+ create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 1, build: build)
+ end
+
+ it 'returns keys' do
+ is_expected.to eq([[build.id, 0], [build.id, 1]])
+ end
+ end
+
+ describe '#delete_keys' do
+ subject { data_store.delete_keys(keys) }
+
+ let(:build) { create(:ci_build) }
+ let(:relation) { build.trace_chunks }
+ let(:keys) { data_store.keys(relation) }
+
+ before do
+ create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 0, build: build)
+ create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 1, build: build)
+ end
+
+ it 'deletes multiple data' do
+ ::Fog::Storage.new(JobArtifactUploader.object_store_credentials).tap do |connection|
+ expect(connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/0.log")[:body]).to be_present
+ expect(connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/1.log")[:body]).to be_present
+ end
+
+ subject
+
+ ::Fog::Storage.new(JobArtifactUploader.object_store_credentials).tap do |connection|
+ expect { connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/0.log")[:body] }.to raise_error(Excon::Error::NotFound)
+ expect { connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/1.log")[:body] }.to raise_error(Excon::Error::NotFound)
+ end
+ end
+ end
+end
--- /dev/null
+++ gitlab-13.6.5/spec/requests/user_sends_null_bytes_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'User sends null bytes as params' do
+ let(:null_byte) { "\u0000" }
+
+ it 'raises a 400 error' do
+ post '/nonexistent', params: { a: "A #{null_byte} nasty string" }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response.body).to eq('Bad Request')
+ end
+end