Commit cec262fc authored by Coung Ngo's avatar Coung Ngo

Merge branch 'master' of gitlab.com:gitlab-org/gitlab into 9838-hide-labels-from-issue-board-cards

parents d6dc962d c47991bc
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33"
stages: stages:
- sync
- prepare - prepare
- quick-test - quick-test
- test - test
...@@ -40,3 +41,4 @@ include: ...@@ -40,3 +41,4 @@ include:
- local: .gitlab/ci/setup.gitlab-ci.yml - local: .gitlab/ci/setup.gitlab-ci.yml
- local: .gitlab/ci/test-metadata.gitlab-ci.yml - local: .gitlab/ci/test-metadata.gitlab-ci.yml
- local: .gitlab/ci/yaml.gitlab-ci.yml - local: .gitlab/ci/yaml.gitlab-ci.yml
- local: .gitlab/ci/releases.gitlab-ci.yml
---
# Syncs any changes pushed to a stable branch to the corresponding CE stable
# branch. We run this prior to any tests so that random failures don't prevent a
# sync.
sync-stable-branch:
# We don't need/want any global before/after commands, so we overwrite these
# settings.
image: alpine:edge
stage: sync
# This job should only run on EE stable branches on the canonical GitLab.com
# repository.
only:
variables:
- $CI_SERVER_HOST == "gitlab.com"
refs:
- /^[\d-]+-stable-ee$/@gitlab-org/gitlab
before_script:
- apk add --no-cache --update curl bash
after_script: []
script:
- bash scripts/sync-stable-branch.sh
...@@ -35,14 +35,12 @@ build-qa-image: ...@@ -35,14 +35,12 @@ build-qa-image:
- echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY} - echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY}
- time docker push ${QA_IMAGE} - time docker push ${QA_IMAGE}
schedule:review-cleanup: .base-review-cleanup:
extends: extends:
- .default-tags - .default-tags
- .default-retry - .default-retry
- .default-only - .default-only
- .only-code-qa-changes - .only-code-qa-changes
- .only-review-schedules
- .except-deploys
stage: prepare stage: prepare
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
allow_failure: true allow_failure: true
...@@ -55,6 +53,16 @@ schedule:review-cleanup: ...@@ -55,6 +53,16 @@ schedule:review-cleanup:
script: script:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb - ruby -rrubygems scripts/review_apps/automated_cleanup.rb
schedule:review-cleanup:
extends:
- .base-review-cleanup
- .only-review-schedules
manual:review-cleanup:
extends:
- .base-review-cleanup
when: manual
.review-build-cng-base: .review-build-cng-base:
extends: extends:
- .default-only - .default-only
......
...@@ -259,9 +259,6 @@ gem 'loofah', '~> 2.2' ...@@ -259,9 +259,6 @@ gem 'loofah', '~> 2.2'
# Working with license # Working with license
gem 'licensee', '~> 8.9' gem 'licensee', '~> 8.9'
# Protect against bruteforcing
gem 'rack-attack', '~> 4.4.1'
# Ace editor # Ace editor
gem 'ace-rails-ap', '~> 4.1.0' gem 'ace-rails-ap', '~> 4.1.0'
...@@ -293,6 +290,9 @@ gem 'base32', '~> 0.3.0' ...@@ -293,6 +290,9 @@ gem 'base32', '~> 0.3.0'
gem "gitlab-license", "~> 1.0" gem "gitlab-license", "~> 1.0"
# Protect against bruteforcing
gem 'rack-attack', '~> 6.2.0'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 2.9' gem 'sentry-raven', '~> 2.9'
......
...@@ -734,8 +734,8 @@ GEM ...@@ -734,8 +734,8 @@ GEM
rack (2.0.7) rack (2.0.7)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (4.4.1) rack-attack (6.2.0)
rack rack (>= 1.0, < 3)
rack-cors (1.0.2) rack-cors (1.0.2)
rack-oauth2 (1.9.3) rack-oauth2 (1.9.3)
activesupport activesupport
...@@ -1256,7 +1256,7 @@ DEPENDENCIES ...@@ -1256,7 +1256,7 @@ DEPENDENCIES
puma (~> 3.12) puma (~> 3.12)
puma_worker_killer puma_worker_killer
rack (~> 2.0.7) rack (~> 2.0.7)
rack-attack (~> 4.4.1) rack-attack (~> 6.2.0)
rack-cors (~> 1.0.0) rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.9.3) rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0) rack-proxy (~> 0.6.0)
......
12.3.0-pre 12.5.0-pre
// capture anything starting with http:// or https://
// up until a disallowed character or whitespace
export const blobLinkRegex = /https?:\/\/[^"<>\\^`{|}\s]+/g;
export default { blobLinkRegex };
...@@ -4,6 +4,10 @@ import Flash from '../../flash'; ...@@ -4,6 +4,10 @@ import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils'; import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils'; import axios from '../../lib/utils/axios_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { blobLinkRegex } from '~/blob/blob_utils';
const SIMPLE_VIEWER_NAME = 'simple';
const RICH_VIEWER_NAME = 'rich';
export default class BlobViewer { export default class BlobViewer {
constructor() { constructor() {
...@@ -21,7 +25,7 @@ export default class BlobViewer { ...@@ -21,7 +25,7 @@ export default class BlobViewer {
} }
static initRichViewer() { static initRichViewer() {
const viewer = document.querySelector('.blob-viewer[data-type="rich"]'); const viewer = document.querySelector(`.blob-viewer[data-type="${RICH_VIEWER_NAME}"]`);
if (!viewer || !viewer.dataset.richType) return; if (!viewer || !viewer.dataset.richType) return;
const initViewer = promise => const initViewer = promise =>
...@@ -61,8 +65,12 @@ export default class BlobViewer { ...@@ -61,8 +65,12 @@ export default class BlobViewer {
this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn'); this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn');
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn'); this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
this.simpleViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="simple"]'); this.simpleViewer = this.$fileHolder[0].querySelector(
this.richViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="rich"]'); `.blob-viewer[data-type="${SIMPLE_VIEWER_NAME}"]`,
);
this.richViewer = this.$fileHolder[0].querySelector(
`.blob-viewer[data-type="${RICH_VIEWER_NAME}"]`,
);
this.initBindings(); this.initBindings();
...@@ -71,10 +79,10 @@ export default class BlobViewer { ...@@ -71,10 +79,10 @@ export default class BlobViewer {
switchToInitialViewer() { switchToInitialViewer() {
const initialViewer = this.$fileHolder[0].querySelector('.blob-viewer:not(.hidden)'); const initialViewer = this.$fileHolder[0].querySelector('.blob-viewer:not(.hidden)');
let initialViewerName = initialViewer.getAttribute('data-type'); let initialViewerName = initialViewer.dataset.type;
if (this.switcher && window.location.hash.indexOf('#L') === 0) { if (this.switcher && window.location.hash.indexOf('#L') === 0) {
initialViewerName = 'simple'; initialViewerName = SIMPLE_VIEWER_NAME;
} }
this.switchToViewer(initialViewerName); this.switchToViewer(initialViewerName);
...@@ -91,35 +99,41 @@ export default class BlobViewer { ...@@ -91,35 +99,41 @@ export default class BlobViewer {
this.copySourceBtn.addEventListener('click', () => { this.copySourceBtn.addEventListener('click', () => {
if (this.copySourceBtn.classList.contains('disabled')) return this.copySourceBtn.blur(); if (this.copySourceBtn.classList.contains('disabled')) return this.copySourceBtn.blur();
return this.switchToViewer('simple'); return this.switchToViewer(SIMPLE_VIEWER_NAME);
}); });
} }
} }
static linkifyURLs(viewer) {
if (viewer.dataset.linkified) return;
document.querySelectorAll('.js-blob-content .code .line').forEach(line => {
// eslint-disable-next-line no-param-reassign
line.innerHTML = line.innerHTML.replace(blobLinkRegex, '<a href="$&">$&</a>');
});
// eslint-disable-next-line no-param-reassign
viewer.dataset.linkified = true;
}
switchViewHandler(e) { switchViewHandler(e) {
const target = e.currentTarget; const target = e.currentTarget;
e.preventDefault(); e.preventDefault();
this.switchToViewer(target.getAttribute('data-viewer')); this.switchToViewer(target.dataset.viewer);
} }
toggleCopyButtonState() { toggleCopyButtonState() {
if (!this.copySourceBtn) return; if (!this.copySourceBtn) return;
if (this.simpleViewer.getAttribute('data-loaded')) { if (this.simpleViewer.dataset.loaded) {
this.copySourceBtn.setAttribute('title', __('Copy file contents')); this.copySourceBtn.dataset.title = __('Copy file contents');
this.copySourceBtn.classList.remove('disabled'); this.copySourceBtn.classList.remove('disabled');
} else if (this.activeViewer === this.simpleViewer) { } else if (this.activeViewer === this.simpleViewer) {
this.copySourceBtn.setAttribute( this.copySourceBtn.dataset.title = __('Wait for the file to load to copy its contents');
'title',
__('Wait for the file to load to copy its contents'),
);
this.copySourceBtn.classList.add('disabled'); this.copySourceBtn.classList.add('disabled');
} else { } else {
this.copySourceBtn.setAttribute( this.copySourceBtn.dataset.title = __('Switch to the source to copy the file contents');
'title',
__('Switch to the source to copy the file contents'),
);
this.copySourceBtn.classList.add('disabled'); this.copySourceBtn.classList.add('disabled');
} }
...@@ -159,6 +173,8 @@ export default class BlobViewer { ...@@ -159,6 +173,8 @@ export default class BlobViewer {
this.$fileHolder.trigger('highlight:line'); this.$fileHolder.trigger('highlight:line');
handleLocationHash(); handleLocationHash();
if (name === SIMPLE_VIEWER_NAME) BlobViewer.linkifyURLs(viewer);
this.toggleCopyButtonState(); this.toggleCopyButtonState();
}) })
.catch(() => new Flash(__('Error loading viewer'))); .catch(() => new Flash(__('Error loading viewer')));
...@@ -166,17 +182,17 @@ export default class BlobViewer { ...@@ -166,17 +182,17 @@ export default class BlobViewer {
static loadViewer(viewerParam) { static loadViewer(viewerParam) {
const viewer = viewerParam; const viewer = viewerParam;
const url = viewer.getAttribute('data-url'); const { url, loaded, loading } = viewer.dataset;
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) { if (!url || loaded || loading) {
return Promise.resolve(viewer); return Promise.resolve(viewer);
} }
viewer.setAttribute('data-loading', 'true'); viewer.dataset.loading = true;
return axios.get(url).then(({ data }) => { return axios.get(url).then(({ data }) => {
viewer.innerHTML = data.html; viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true'); viewer.dataset.loaded = true;
return viewer; return viewer;
}); });
......
...@@ -4,7 +4,8 @@ import $ from 'jquery'; ...@@ -4,7 +4,8 @@ import $ from 'jquery';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import TemplateSelectorMediator from '../blob/file_template_mediator'; import { blobLinkRegex } from '~/blob/blob_utils';
import TemplateSelectorMediator from '~/blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils'; import getModeByFileExtension from '~/lib/utils/ace_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown'; import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
...@@ -17,6 +18,7 @@ export default class EditBlob { ...@@ -17,6 +18,7 @@ export default class EditBlob {
this.initModePanesAndLinks(); this.initModePanesAndLinks();
this.initSoftWrap(); this.initSoftWrap();
this.initFileSelectors(); this.initFileSelectors();
this.initBlobContentLinkClickability();
} }
configureAceEditor() { configureAceEditor() {
...@@ -89,6 +91,22 @@ export default class EditBlob { ...@@ -89,6 +91,22 @@ export default class EditBlob {
return this.editor.focus(); return this.editor.focus();
} }
initBlobContentLinkClickability() {
this.editor.renderer.on('afterRender', () => {
document.querySelectorAll('.ace_text-layer .ace_line > *').forEach(token => {
if (token.dataset.linkified || !token.textContent.includes('http')) return;
// eslint-disable-next-line no-param-reassign
token.innerHTML = token.innerHTML.replace(
blobLinkRegex,
'<a target="_blank" href="$&">$&</a>',
);
// eslint-disable-next-line no-param-reassign
token.dataset.linkified = true;
});
});
}
initSoftWrap() { initSoftWrap() {
this.isSoftWrapped = false; this.isSoftWrapped = false;
this.$toggleButton = $('.soft-wrap-toggle'); this.$toggleButton = $('.soft-wrap-toggle');
......
<script> <script>
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { GlEmptyState, GlButton, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui'; import {
GlEmptyState,
GlButton,
GlLink,
GlLoadingIcon,
GlTable,
GlSearchBoxByType,
} from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
...@@ -20,6 +27,7 @@ export default { ...@@ -20,6 +27,7 @@ export default {
GlLink, GlLink,
GlLoadingIcon, GlLoadingIcon,
GlTable, GlTable,
GlSearchBoxByType,
Icon, Icon,
TimeAgo, TimeAgo,
}, },
...@@ -48,8 +56,17 @@ export default { ...@@ -48,8 +56,17 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
errorSearchQuery: '',
};
},
computed: { computed: {
...mapState(['errors', 'externalUrl', 'loading']), ...mapState(['errors', 'externalUrl', 'loading']),
...mapGetters(['filterErrorsByTitle']),
filteredErrors() {
return this.errorSearchQuery ? this.filterErrorsByTitle(this.errorSearchQuery) : this.errors;
},
}, },
created() { created() {
if (this.errorTrackingEnabled) { if (this.errorTrackingEnabled) {
...@@ -71,10 +88,17 @@ export default { ...@@ -71,10 +88,17 @@ export default {
<gl-loading-icon :size="3" /> <gl-loading-icon :size="3" />
</div> </div>
<div v-else> <div v-else>
<div class="d-flex justify-content-end"> <div class="d-flex flex-row justify-content-around bg-secondary border">
<gl-search-box-by-type
v-model="errorSearchQuery"
class="col-lg-10 m-3 p-0"
:placeholder="__('Search or filter results...')"
type="search"
autofocus
/>
<gl-button <gl-button
v-track-event="trackViewInSentryOptions(externalUrl)" v-track-event="trackViewInSentryOptions(externalUrl)"
class="my-3 ml-auto" class="m-3"
variant="primary" variant="primary"
:href="externalUrl" :href="externalUrl"
target="_blank" target="_blank"
...@@ -84,7 +108,14 @@ export default { ...@@ -84,7 +108,14 @@ export default {
</gl-button> </gl-button>
</div> </div>
<gl-table :items="errors" :fields="$options.fields" :show-empty="true" fixed stacked="sm"> <gl-table
class="mt-3"
:items="filteredErrors"
:fields="$options.fields"
:show-empty="true"
fixed
stacked="sm"
>
<template slot="HEAD_events" slot-scope="data"> <template slot="HEAD_events" slot-scope="data">
<div class="text-md-right">{{ data.label }}</div> <div class="text-md-right">{{ data.label }}</div>
</template> </template>
......
export const filterErrorsByTitle = state => errorQuery =>
state.errors.filter(error => error.title.match(new RegExp(`${errorQuery}`, 'i')));
export default () => {};
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import * as actions from './actions'; import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations'; import mutations from './mutations';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -14,6 +15,7 @@ export const createStore = () => ...@@ -14,6 +15,7 @@ export const createStore = () =>
}, },
actions, actions,
mutations, mutations,
getters,
}); });
export default createStore(); export default createStore();
<script> <script>
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { GlLink, GlButton, GlTooltip } from '@gitlab/ui'; import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui';
import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils'; import { roundOffFloat } from '~/lib/utils/common_utils';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { chartHeight, graphTypes, lineTypes, symbolSizes, dateFormats } from '../../constants'; import { chartHeight, graphTypes, lineTypes, symbolSizes, dateFormats } from '../../constants';
import { makeDataSeries } from '~/helpers/monitor_helper'; import { makeDataSeries } from '~/helpers/monitor_helper';
import { graphDataValidatorForValues } from '../../utils'; import { graphDataValidatorForValues } from '../../utils';
let debouncedResize;
export default { export default {
components: { components: {
GlAreaChart, GlAreaChart,
...@@ -22,6 +20,9 @@ export default { ...@@ -22,6 +20,9 @@ export default {
GlLink, GlLink,
Icon, Icon,
}, },
directives: {
GlResizeObserverDirective,
},
inheritAttrs: false, inheritAttrs: false,
props: { props: {
graphData: { graphData: {
...@@ -29,10 +30,6 @@ export default { ...@@ -29,10 +30,6 @@ export default {
required: true, required: true,
validator: graphDataValidatorForValues.bind(null, false), validator: graphDataValidatorForValues.bind(null, false),
}, },
containerWidth: {
type: Number,
required: true,
},
deploymentData: { deploymentData: {
type: Array, type: Array,
required: false, required: false,
...@@ -206,21 +203,13 @@ export default { ...@@ -206,21 +203,13 @@ export default {
return `${this.graphData.y_label}`; return `${this.graphData.y_label}`;
}, },
}, },
watch: {
containerWidth: 'onResize',
},
mounted() { mounted() {
const graphTitleEl = this.$refs.graphTitle; const graphTitleEl = this.$refs.graphTitle;
if (graphTitleEl && graphTitleEl.scrollWidth > graphTitleEl.offsetWidth) { if (graphTitleEl && graphTitleEl.scrollWidth > graphTitleEl.offsetWidth) {
this.showTitleTooltip = true; this.showTitleTooltip = true;
} }
}, },
beforeDestroy() {
window.removeEventListener('resize', debouncedResize);
},
created() { created() {
debouncedResize = debounceByAnimationFrame(this.onResize);
window.addEventListener('resize', debouncedResize);
this.setSvg('rocket'); this.setSvg('rocket');
this.setSvg('scroll-handle'); this.setSvg('scroll-handle');
}, },
...@@ -276,7 +265,7 @@ export default { ...@@ -276,7 +265,7 @@ export default {
</script> </script>
<template> <template>
<div class="prometheus-graph"> <div v-gl-resize-observer-directive="onResize" class="prometheus-graph">
<div class="prometheus-graph-header"> <div class="prometheus-graph-header">
<h5 <h5
ref="graphTitle" ref="graphTitle"
......
...@@ -22,12 +22,9 @@ import MonitorTimeSeriesChart from './charts/time_series.vue'; ...@@ -22,12 +22,9 @@ import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorSingleStatChart from './charts/single_stat.vue'; import MonitorSingleStatChart from './charts/single_stat.vue';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import { sidebarAnimationDuration } from '../constants';
import TrackEventDirective from '~/vue_shared/directives/track_event'; import TrackEventDirective from '~/vue_shared/directives/track_event';
import { getTimeDiff, isValidDate, downloadCSVOptions, generateLinkToChartOptions } from '../utils'; import { getTimeDiff, isValidDate, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
let sidebarMutationObserver;
export default { export default {
components: { components: {
VueDraggable, VueDraggable,
...@@ -167,7 +164,6 @@ export default { ...@@ -167,7 +164,6 @@ export default {
data() { data() {
return { return {
state: 'gettingStarted', state: 'gettingStarted',
elWidth: 0,
formIsValid: null, formIsValid: null,
selectedTimeWindow: {}, selectedTimeWindow: {},
isRearrangingPanels: false, isRearrangingPanels: false,
...@@ -214,11 +210,6 @@ export default { ...@@ -214,11 +210,6 @@ export default {
projectPath: this.projectPath, projectPath: this.projectPath,
}); });
}, },
beforeDestroy() {
if (sidebarMutationObserver) {
sidebarMutationObserver.disconnect();
}
},
mounted() { mounted() {
if (!this.hasMetrics) { if (!this.hasMetrics) {
this.setGettingStartedEmptyState(); this.setGettingStartedEmptyState();
...@@ -239,13 +230,6 @@ export default { ...@@ -239,13 +230,6 @@ export default {
} else { } else {
this.fetchData(range); this.fetchData(range);
} }
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
attributes: true,
childList: false,
subtree: false,
});
} }
}, },
methods: { methods: {
...@@ -306,11 +290,6 @@ export default { ...@@ -306,11 +290,6 @@ export default {
hideAddMetricModal() { hideAddMetricModal() {
this.$refs.addMetricModal.hide(); this.$refs.addMetricModal.hide();
}, },
onSidebarMutation() {
setTimeout(() => {
this.elWidth = this.$el.clientWidth;
}, sidebarAnimationDuration);
},
toggleRearrangingPanels() { toggleRearrangingPanels() {
this.isRearrangingPanels = !this.isRearrangingPanels; this.isRearrangingPanels = !this.isRearrangingPanels;
}, },
...@@ -503,7 +482,6 @@ export default { ...@@ -503,7 +482,6 @@ export default {
generateLink(groupData.group, graphData.title, graphData.y_label) generateLink(groupData.group, graphData.title, graphData.y_label)
" "
:graph-data="graphData" :graph-data="graphData"
:dashboard-width="elWidth"
:alerts-endpoint="alertsEndpoint" :alerts-endpoint="alertsEndpoint"
:prometheus-alerts-available="prometheusAlertsAvailable" :prometheus-alerts-available="prometheusAlertsAvailable"
:index="`${index}-${graphIndex}`" :index="`${index}-${graphIndex}`"
...@@ -520,7 +498,6 @@ export default { ...@@ -520,7 +498,6 @@ export default {
:graph-data="graphData" :graph-data="graphData"
:deployment-data="deploymentData" :deployment-data="deploymentData"
:thresholds="getGraphAlertValues(graphData.queries)" :thresholds="getGraphAlertValues(graphData.queries)"
:container-width="elWidth"
:project-path="projectPath" :project-path="projectPath"
group-id="monitor-time-series-chart" group-id="monitor-time-series-chart"
> >
......
...@@ -45,7 +45,7 @@ export default { ...@@ -45,7 +45,7 @@ export default {
<div v-if="showPanels" class="card prometheus-panel"> <div v-if="showPanels" class="card prometheus-panel">
<div class="card-header d-flex align-items-center"> <div class="card-header d-flex align-items-center">
<h4 class="flex-grow-1">{{ name }}</h4> <h4 class="flex-grow-1">{{ name }}</h4>
<a role="button" @click="collapse"> <a role="button" class="js-graph-group-toggle" @click="collapse">
<icon :size="16" :aria-label="__('Toggle collapse')" :name="caretIcon" /> <icon :size="16" :aria-label="__('Toggle collapse')" :name="caretIcon" />
</a> </a>
</div> </div>
......
...@@ -40,10 +40,6 @@ export default { ...@@ -40,10 +40,6 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
dashboardWidth: {
type: Number,
required: true,
},
index: { index: {
type: String, type: String,
required: false, required: false,
...@@ -103,7 +99,6 @@ export default { ...@@ -103,7 +99,6 @@ export default {
:deployment-data="deploymentData" :deployment-data="deploymentData"
:project-path="projectPath" :project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.queries)" :thresholds="getGraphAlertValues(graphData.queries)"
:container-width="dashboardWidth"
group-id="monitor-area-chart" group-id="monitor-area-chart"
> >
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
......
import { __ } from '~/locale';
<script>
export default {
data() {
return {
inputEnabled: false,
urlOrRequestId: '',
};
},
methods: {
toggleInput() {
this.inputEnabled = !this.inputEnabled;
},
addRequest() {
this.$emit('add-request', this.urlOrRequestId);
this.clearForm();
},
clearForm() {
this.urlOrRequestId = '';
this.toggleInput();
},
},
};
</script>
<template>
<div id="peek-view-add-request" class="view">
<form class="form-inline" @submit.prevent>
<button
class="btn-blank btn-link bold"
type="button"
:title="__(`Add request manually`)"
@click="toggleInput"
>
+
</button>
<input
v-if="inputEnabled"
v-model="urlOrRequestId"
type="text"
:placeholder="__(`URL or request ID`)"
class="form-control form-control-sm d-inline-block ml-1"
@keyup.enter="addRequest"
@keyup.esc="clearForm"
/>
</form>
</div>
</template>
<script> <script>
import { glEmojiTag } from '~/emoji'; import { glEmojiTag } from '~/emoji';
import AddRequest from './add_request.vue';
import DetailedMetric from './detailed_metric.vue'; import DetailedMetric from './detailed_metric.vue';
import RequestSelector from './request_selector.vue'; import RequestSelector from './request_selector.vue';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
export default { export default {
components: { components: {
AddRequest,
DetailedMetric, DetailedMetric,
RequestSelector, RequestSelector,
}, },
...@@ -118,6 +120,7 @@ export default { ...@@ -118,6 +120,7 @@ export default {
> >
<a :href="currentRequest.details.tracing.tracing_url">{{ s__('PerformanceBar|trace') }}</a> <a :href="currentRequest.details.tracing.tracing_url">{{ s__('PerformanceBar|trace') }}</a>
</div> </div>
<add-request v-on="$listeners" />
<request-selector <request-selector
v-if="currentRequest" v-if="currentRequest"
:current-request="currentRequest" :current-request="currentRequest"
......
import Vue from 'vue'; import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import PerformanceBarService from './services/performance_bar_service'; import PerformanceBarService from './services/performance_bar_service';
import PerformanceBarStore from './stores/performance_bar_store'; import PerformanceBarStore from './stores/performance_bar_store';
...@@ -32,6 +34,15 @@ export default ({ container }) => ...@@ -32,6 +34,15 @@ export default ({ container }) =>
PerformanceBarService.removeInterceptor(this.interceptor); PerformanceBarService.removeInterceptor(this.interceptor);
}, },
methods: { methods: {
addRequestManually(urlOrRequestId) {
if (urlOrRequestId.startsWith('https://') || urlOrRequestId.startsWith('http://')) {
// We don't need to do anything with the response, we just
// want to trace the request.
axios.get(urlOrRequestId);
} else {
this.loadRequestDetails(urlOrRequestId, urlOrRequestId);
}
},
loadRequestDetails(requestId, requestUrl) { loadRequestDetails(requestId, requestUrl) {
if (!this.store.canTrackRequest(requestUrl)) { if (!this.store.canTrackRequest(requestUrl)) {
return; return;
...@@ -58,6 +69,9 @@ export default ({ container }) => ...@@ -58,6 +69,9 @@ export default ({ container }) =>
peekUrl: this.peekUrl, peekUrl: this.peekUrl,
profileUrl: this.profileUrl, profileUrl: this.profileUrl,
}, },
on: {
'add-request': this.addRequestManually,
},
}); });
}, },
}); });
...@@ -125,19 +125,21 @@ export default { ...@@ -125,19 +125,21 @@ export default {
</div> </div>
<div class="commit-actions flex-row"> <div class="commit-actions flex-row">
<div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div> <div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div>
<gl-link <div class="ci-status-link">
v-if="commit.latestPipeline" <gl-link
v-gl-tooltip v-if="commit.latestPipeline"
:href="commit.latestPipeline.detailedStatus.detailsPath" v-gl-tooltip
:title="statusTitle" :href="commit.latestPipeline.detailedStatus.detailsPath"
class="js-commit-pipeline" :title="statusTitle"
> class="js-commit-pipeline"
<ci-icon >
:status="commit.latestPipeline.detailedStatus" <ci-icon
:size="24" :status="commit.latestPipeline.detailedStatus"
:aria-label="statusTitle" :size="24"
/> :aria-label="statusTitle"
</gl-link> />
</gl-link>
</div>
<div class="commit-sha-group d-flex"> <div class="commit-sha-group d-flex">
<div class="label label-monospace monospace"> <div class="label label-monospace monospace">
{{ showCommitId }} {{ showCommitId }}
......
...@@ -102,7 +102,6 @@ export function initUserTracking() { ...@@ -102,7 +102,6 @@ export function initUserTracking() {
window.snowplow('enableActivityTracking', 30, 30); window.snowplow('enableActivityTracking', 30, 30);
window.snowplow('trackPageView'); // must be after enableActivityTracking window.snowplow('trackPageView'); // must be after enableActivityTracking
if (opts.userId) window.snowplow('setUserId', opts.userId);
if (opts.formTracking) window.snowplow('enableFormTracking'); if (opts.formTracking) window.snowplow('enableFormTracking');
if (opts.linkClickTracking) window.snowplow('enableLinkClickTracking'); if (opts.linkClickTracking) window.snowplow('enableLinkClickTracking');
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
@import 'framework/variables_overrides'; @import 'framework/variables_overrides';
@import 'framework/mixins'; @import 'framework/mixins';
@import '@gitlab/ui/scss/gitlab_ui'; @import '@gitlab/ui/src/scss/gitlab_ui';
@import 'bootstrap_migration'; @import 'bootstrap_migration';
@import 'framework/layout'; @import 'framework/layout';
......
...@@ -258,6 +258,17 @@ ...@@ -258,6 +258,17 @@
} }
} }
} }
.file-editor {
.ace_underline {
text-decoration: none;
}
.ace_line a {
pointer-events: auto;
color: inherit;
}
}
} }
span.idiff { span.idiff {
......
.memory-graph-container { .memory-graph-container {
svg { svg {
background: $white-light; background: $white-light;
cursor: pointer; border: 1px solid $gray-200;
&:hover {
box-shadow: 0 0 4px $gray-darkest inset;
}
} }
path { path {
......
...@@ -29,3 +29,12 @@ ...@@ -29,3 +29,12 @@
color: $link; color: $link;
} }
} }
// Links to URLs, emails, or dependencies
.code .line a {
color: inherit;
&:hover {
text-decoration: underline;
}
}
...@@ -193,11 +193,6 @@ $dark-il: #de935f; ...@@ -193,11 +193,6 @@ $dark-il: #de935f;
color: $dark-highlight-color !important; color: $dark-highlight-color !important;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $dark-na;
}
.hll { background-color: $dark-hll-bg; } .hll { background-color: $dark-hll-bg; }
.c { color: $dark-c; } /* Comment */ .c { color: $dark-c; } /* Comment */
.err { color: $dark-err; } /* Error */ .err { color: $dark-err; } /* Error */
......
...@@ -193,11 +193,6 @@ $monokai-gi: #a6e22e; ...@@ -193,11 +193,6 @@ $monokai-gi: #a6e22e;
color: $black !important; color: $black !important;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $monokai-k;
}
.hll { background-color: $monokai-hll; } .hll { background-color: $monokai-hll; }
.c { color: $monokai-c; } /* Comment */ .c { color: $monokai-c; } /* Comment */
.err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */ .err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */
......
...@@ -143,12 +143,6 @@ ...@@ -143,12 +143,6 @@
background-color: $white-normal; background-color: $white-normal;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $gl-text-color;
text-decoration: underline;
}
.hll { background-color: $white-light; } .hll { background-color: $white-light; }
.gd { .gd {
......
...@@ -196,11 +196,6 @@ $solarized-dark-il: #2aa198; ...@@ -196,11 +196,6 @@ $solarized-dark-il: #2aa198;
background-color: $solarized-dark-highlight !important; background-color: $solarized-dark-highlight !important;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $solarized-dark-kd;
}
/* Solarized Dark /* Solarized Dark
For use with Jekyll and Pygments For use with Jekyll and Pygments
......
...@@ -204,11 +204,6 @@ $solarized-light-il: #2aa198; ...@@ -204,11 +204,6 @@ $solarized-light-il: #2aa198;
background-color: $solarized-light-highlight !important; background-color: $solarized-light-highlight !important;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $solarized-light-kd;
}
/* Solarized Light /* Solarized Light
For use with Jekyll and Pygments For use with Jekyll and Pygments
......
...@@ -209,11 +209,6 @@ span.highlight_word { ...@@ -209,11 +209,6 @@ span.highlight_word {
background-color: $white-highlight !important; background-color: $white-highlight !important;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $white-nb;
}
.hll { background-color: $white-hll-bg; } .hll { background-color: $white-hll-bg; }
.c { color: $white-c; .c { color: $white-c;
......
...@@ -18,6 +18,11 @@ ...@@ -18,6 +18,11 @@
width: 200px; width: 200px;
} }
input {
color: $gl-gray-400;
width: $input-short-width - 60px;
}
&.disabled { &.disabled {
display: none; display: none;
} }
...@@ -25,7 +30,8 @@ ...@@ -25,7 +30,8 @@
&.production { &.production {
background-color: $perf-bar-production; background-color: $perf-bar-production;
select { select,
input {
background: $perf-bar-production; background: $perf-bar-production;
} }
} }
...@@ -33,7 +39,8 @@ ...@@ -33,7 +39,8 @@
&.staging { &.staging {
background-color: $perf-bar-staging; background-color: $perf-bar-staging;
select { select,
input {
background: $perf-bar-staging; background: $perf-bar-staging;
} }
} }
...@@ -41,7 +48,8 @@ ...@@ -41,7 +48,8 @@
&.development { &.development {
background-color: $perf-bar-development; background-color: $perf-bar-development;
select { select,
input {
background: $perf-bar-development; background: $perf-bar-development;
} }
} }
......
...@@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base ...@@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
include WithPerformanceBar include WithPerformanceBar
include SessionlessAuthentication include SessionlessAuthentication
include SessionsHelper
include ConfirmEmailWarning include ConfirmEmailWarning
include Gitlab::Tracking::ControllerConcern include Gitlab::Tracking::ControllerConcern
include Gitlab::Experimentation::ControllerConcern include Gitlab::Experimentation::ControllerConcern
...@@ -35,7 +36,7 @@ class ApplicationController < ActionController::Base ...@@ -35,7 +36,7 @@ class ApplicationController < ActionController::Base
around_action :set_session_storage around_action :set_session_storage
after_action :set_page_title_header, if: :json_request? after_action :set_page_title_header, if: :json_request?
after_action :limit_unauthenticated_session_times after_action :limit_session_time, if: -> { !current_user }
protect_from_forgery with: :exception, prepend: true protect_from_forgery with: :exception, prepend: true
...@@ -101,24 +102,6 @@ class ApplicationController < ActionController::Base ...@@ -101,24 +102,6 @@ class ApplicationController < ActionController::Base
end end
end end
# By default, all sessions are given the same expiration time configured in
# the session store (e.g. 1 week). However, unauthenticated users can
# generate a lot of sessions, primarily for CSRF verification. It makes
# sense to reduce the TTL for unauthenticated to something much lower than
# the default (e.g. 1 hour) to limit Redis memory. In addition, Rails
# creates a new session after login, so the short TTL doesn't even need to
# be extended.
def limit_unauthenticated_session_times
return if current_user
# Rack sets this header, but not all tests may have it: https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L251-L259
return unless request.env['rack.session.options']
# This works because Rack uses these options every time a request is handled:
# https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L342
request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay']
end
def render(*args) def render(*args)
super.tap do super.tap do
# Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse # Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse
......
...@@ -142,6 +142,7 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -142,6 +142,7 @@ class Clusters::ClustersController < Clusters::BaseController
:environment_scope, :environment_scope,
:managed, :managed,
:base_domain, :base_domain,
:management_project_id,
platform_kubernetes_attributes: [ platform_kubernetes_attributes: [
:api_url, :api_url,
:token, :token,
...@@ -155,6 +156,7 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -155,6 +156,7 @@ class Clusters::ClustersController < Clusters::BaseController
:environment_scope, :environment_scope,
:managed, :managed,
:base_domain, :base_domain,
:management_project_id,
platform_kubernetes_attributes: [ platform_kubernetes_attributes: [
:namespace :namespace
] ]
......
...@@ -47,7 +47,7 @@ module RoutableActions ...@@ -47,7 +47,7 @@ module RoutableActions
canonical_path = routable.full_path canonical_path = routable.full_path
if canonical_path != requested_full_path if canonical_path != requested_full_path
if canonical_path.casecmp(requested_full_path) != 0 if !request.xhr? && request.format.html? && canonical_path.casecmp(requested_full_path) != 0
flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_full_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path." flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_full_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path."
end end
......
...@@ -16,7 +16,7 @@ module Groups ...@@ -16,7 +16,7 @@ module Groups
render json: ContainerRepositoriesSerializer render json: ContainerRepositoriesSerializer
.new(current_user: current_user) .new(current_user: current_user)
.represent(@images) .represent_read_only(@images)
end end
end end
end end
......
...@@ -156,14 +156,21 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -156,14 +156,21 @@ class Projects::PipelinesController < Projects::ApplicationController
def test_report def test_report
return unless Feature.enabled?(:junit_pipeline_view, project) return unless Feature.enabled?(:junit_pipeline_view, project)
if pipeline_test_report == :error respond_to do |format|
render json: { status: :error_parsing_report } format.html do
return render 'show'
end end
render json: TestReportSerializer format.json do
.new(current_user: @current_user) if pipeline_test_report == :error
.represent(pipeline_test_report) render json: { status: :error_parsing_report }
else
render json: TestReportSerializer
.new(current_user: @current_user)
.represent(pipeline_test_report)
end
end
end
end end
private private
......
...@@ -371,6 +371,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -371,6 +371,7 @@ class ProjectsController < Projects::ApplicationController
:path, :path,
:printing_merge_request_link_enabled, :printing_merge_request_link_enabled,
:public_builds, :public_builds,
:remove_source_branch_after_merge,
:request_access_enabled, :request_access_enabled,
:runners_token, :runners_token,
:tag_list, :tag_list,
......
...@@ -188,11 +188,9 @@ class TodosFinder ...@@ -188,11 +188,9 @@ class TodosFinder
end end
def by_state(items) def by_state(items)
if params[:state].to_s == 'done' return items.pending if params[:state].blank?
items.done
else items.with_states(params[:state])
items.pending
end
end end
def by_type(items) def by_type(items)
......
...@@ -66,6 +66,7 @@ module Types ...@@ -66,6 +66,7 @@ module Types
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :remove_source_branch_after_merge, GraphQL::BOOLEAN_TYPE, null: true, description: 'Remove the source branch by default after merge'
field :namespace, Types::NamespaceType, null: true # rubocop:disable Graphql/Descriptions field :namespace, Types::NamespaceType, null: true # rubocop:disable Graphql/Descriptions
field :group, Types::GroupType, null: true # rubocop:disable Graphql/Descriptions field :group, Types::GroupType, null: true # rubocop:disable Graphql/Descriptions
......
...@@ -96,8 +96,9 @@ module SearchHelper ...@@ -96,8 +96,9 @@ module SearchHelper
result result
end end
def search_blob_title(project, filename) # Overriden in EE
filename def search_blob_title(project, path)
path
end end
def search_service def search_service
......
...@@ -4,4 +4,20 @@ module SessionsHelper ...@@ -4,4 +4,20 @@ module SessionsHelper
def unconfirmed_email? def unconfirmed_email?
flash[:alert] == t(:unconfirmed, scope: [:devise, :failure]) flash[:alert] == t(:unconfirmed, scope: [:devise, :failure])
end end
# By default, all sessions are given the same expiration time configured in
# the session store (e.g. 1 week). However, unauthenticated users can
# generate a lot of sessions, primarily for CSRF verification. It makes
# sense to reduce the TTL for unauthenticated to something much lower than
# the default (e.g. 1 hour) to limit Redis memory. In addition, Rails
# creates a new session after login, so the short TTL doesn't even need to
# be extended.
def limit_session_time
# Rack sets this header, but not all tests may have it: https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L251-L259
return unless request.env['rack.session.options']
# This works because Rack uses these options every time a request is handled:
# https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L342
request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay']
end
end end
...@@ -78,12 +78,42 @@ module Clusters ...@@ -78,12 +78,42 @@ module Clusters
"controller" => { "controller" => {
"config" => { "config" => {
"enable-modsecurity" => "true", "enable-modsecurity" => "true",
"enable-owasp-modsecurity-crs" => "true" "enable-owasp-modsecurity-crs" => "true",
} "modsecurity.conf" => modsecurity_config_content
},
"extraVolumeMounts" => [
{
"name" => "modsecurity-template-volume",
"mountPath" => "/etc/nginx/modsecurity/modsecurity.conf",
"subPath" => "modsecurity.conf"
}
],
"extraVolumes" => [
{
"name" => "modsecurity-template-volume",
"configMap" => {
"name" => "ingress-nginx-ingress-controller",
"items" => [
{
"key" => "modsecurity.conf",
"path" => "modsecurity.conf"
}
]
}
}
]
} }
} }
end end
def modsecurity_config_content
File.read(modsecurity_config_file_path)
end
def modsecurity_config_file_path
Rails.root.join('vendor', 'ingress', 'modsecurity.conf')
end
def content_values def content_values
YAML.load_file(chart_values_file).deep_merge!(specification) YAML.load_file(chart_values_file).deep_merge!(specification)
end end
......
...@@ -9,8 +9,6 @@ module Clusters ...@@ -9,8 +9,6 @@ module Clusters
self.table_name = 'clusters' self.table_name = 'clusters'
PROJECT_ONLY_APPLICATIONS = {
}.freeze
APPLICATIONS = { APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm, Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress, Applications::Ingress.application_name => Applications::Ingress,
...@@ -20,7 +18,7 @@ module Clusters ...@@ -20,7 +18,7 @@ module Clusters
Applications::Jupyter.application_name => Applications::Jupyter, Applications::Jupyter.application_name => Applications::Jupyter,
Applications::Knative.application_name => Applications::Knative, Applications::Knative.application_name => Applications::Knative,
Applications::ElasticStack.application_name => Applications::ElasticStack Applications::ElasticStack.application_name => Applications::ElasticStack
}.merge(PROJECT_ONLY_APPLICATIONS).freeze }.freeze
DEFAULT_ENVIRONMENT = '*' DEFAULT_ENVIRONMENT = '*'
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN' KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
...@@ -117,6 +115,8 @@ module Clusters ...@@ -117,6 +115,8 @@ module Clusters
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) } scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
scope :for_project_namespace, -> (namespace_id) { joins(:projects).where(projects: { namespace_id: namespace_id }) }
def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc) def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc)
return [] if clusterable.is_a?(Instance) return [] if clusterable.is_a?(Instance)
......
...@@ -20,7 +20,7 @@ module Clusters ...@@ -20,7 +20,7 @@ module Clusters
.with .with
.recursive(cte.to_arel) .recursive(cte.to_arel)
.from(cte_alias) .from(cte_alias)
.order(DEPTH_COLUMN => :asc) .order(depth_order_clause)
end end
private private
...@@ -40,7 +40,7 @@ module Clusters ...@@ -40,7 +40,7 @@ module Clusters
end end
if clusterable.is_a?(::Project) && include_management_project if clusterable.is_a?(::Project) && include_management_project
cte << management_clusters_query cte << same_namespace_management_clusters_query
end end
cte << base_query cte << base_query
...@@ -49,13 +49,42 @@ module Clusters ...@@ -49,13 +49,42 @@ module Clusters
cte cte
end end
# Returns project-level clusters where the project is the management project
# for the cluster. The management project has to be in the same namespace /
# group as the cluster's project.
#
# Support for management project in sub-groups is planned in
# https://gitlab.com/gitlab-org/gitlab/issues/34650
#
# NB: group_parent_id is un-used but we still need to match the same number of
# columns as other queries in the CTE.
def same_namespace_management_clusters_query
clusterable.management_clusters
.project_type
.select([clusters_star, 'NULL AS group_parent_id', "0 AS #{DEPTH_COLUMN}"])
.for_project_namespace(clusterable.namespace_id)
end
# Management clusters should be first in the hierarchy so we use 0 for the # Management clusters should be first in the hierarchy so we use 0 for the
# depth column. # depth column.
# #
# group_parent_id is un-used but we still need to match the same number of # Only applicable if the clusterable is a project (most especially when
# columns as other queries in the CTE. # requesting project.deployment_platform).
def management_clusters_query def depth_order_clause
clusterable.management_clusters.select([clusters_star, 'NULL AS group_parent_id', "0 AS #{DEPTH_COLUMN}"]) return { DEPTH_COLUMN => :asc } unless clusterable.is_a?(::Project) && include_management_project
order = <<~SQL
(CASE clusters.management_project_id
WHEN :project_id THEN 0
ELSE #{DEPTH_COLUMN}
END) ASC
SQL
values = {
project_id: clusterable.id
}
model.sanitize_sql_array([Arel.sql(order), values])
end end
def group_clusters_base_query def group_clusters_base_query
......
...@@ -12,7 +12,7 @@ module DeploymentPlatform ...@@ -12,7 +12,7 @@ module DeploymentPlatform
private private
def cluster_management_project_enabled? def cluster_management_project_enabled?
Feature.enabled?(:cluster_management_project, default_enabled: true) Feature.enabled?(:cluster_management_project, self)
end end
def find_deployment_platform(environment) def find_deployment_platform(environment)
......
...@@ -11,7 +11,7 @@ class ContainerRepository < ApplicationRecord ...@@ -11,7 +11,7 @@ class ContainerRepository < ApplicationRecord
delegate :client, to: :registry delegate :client, to: :registry
scope :ordered, -> { order(:name) } scope :ordered, -> { order(:name) }
scope :with_api_entity_associations, -> { preload(:project) } scope :with_api_entity_associations, -> { preload(project: [:route, { namespace: :route }]) }
# rubocop: disable CodeReuse/ServiceClass # rubocop: disable CodeReuse/ServiceClass
def registry def registry
......
...@@ -91,6 +91,7 @@ class Project < ApplicationRecord ...@@ -91,6 +91,7 @@ class Project < ApplicationRecord
default_value_for :wiki_enabled, gitlab_config_features.wiki default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for :only_allow_merge_if_all_discussions_are_resolved, false default_value_for :only_allow_merge_if_all_discussions_are_resolved, false
default_value_for :remove_source_branch_after_merge, true
add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required } add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
......
# frozen_string_literal: true # frozen_string_literal: true
class Release < ApplicationRecord class Release < ApplicationRecord
include Presentable
include CacheMarkdownField include CacheMarkdownField
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
......
# frozen_string_literal: true
class ReleasePresenter < Gitlab::View::Presenter::Delegated
include ActionView::Helpers::UrlHelper
presents :release
delegate :project, :tag, to: :release
def commit_path
return unless release.commit && can_download_code?
project_commit_path(project, release.commit.id)
end
def tag_path
return unless can_download_code?
project_tag_path(project, release.tag)
end
def merge_requests_url
return unless release_mr_issue_urls_available?
project_merge_requests_url(project, params_for_issues_and_mrs)
end
def issues_url
return unless release_mr_issue_urls_available?
project_issues_url(project, params_for_issues_and_mrs)
end
private
def can_download_code?
can?(current_user, :download_code, project)
end
def params_for_issues_and_mrs
{ scope: 'all', state: 'opened', release_tag: release.tag }
end
def release_mr_issue_urls_available?
::Feature.enabled?(:release_mr_issue_urls, project)
end
end
...@@ -2,4 +2,8 @@ ...@@ -2,4 +2,8 @@
class ContainerRepositoriesSerializer < BaseSerializer class ContainerRepositoriesSerializer < BaseSerializer
entity ContainerRepositoryEntity entity ContainerRepositoryEntity
def represent_read_only(resource)
represent(resource, except: [:destroy_path])
end
end end
...@@ -21,6 +21,8 @@ class MergeRequestDiffEntity < Grape::Entity ...@@ -21,6 +21,8 @@ class MergeRequestDiffEntity < Grape::Entity
expose :latest?, as: :latest expose :latest?, as: :latest
expose :short_commit_sha do |merge_request_diff| expose :short_commit_sha do |merge_request_diff|
next unless merge_request_diff.head_commit_sha
short_sha(merge_request_diff.head_commit_sha) short_sha(merge_request_diff.head_commit_sha)
end end
......
...@@ -64,19 +64,13 @@ module Clusters ...@@ -64,19 +64,13 @@ module Clusters
end end
def invalid_application? def invalid_application?
unknown_application? || (!cluster.project_type? && project_only_application?) || (application_name == Applications::ElasticStack.application_name && !Feature.enabled?(:enable_cluster_application_elastic_stack)) unknown_application? || (application_name == Applications::ElasticStack.application_name && !Feature.enabled?(:enable_cluster_application_elastic_stack))
end end
def unknown_application? def unknown_application?
Clusters::Cluster::APPLICATIONS.keys.exclude?(application_name) Clusters::Cluster::APPLICATIONS.keys.exclude?(application_name)
end end
# These applications will need extra configuration to enable them to work
# with groups of projects
def project_only_application?
Clusters::Cluster::PROJECT_ONLY_APPLICATIONS.include?(application_name)
end
def application_name def application_name
params[:application] params[:application]
end end
......
...@@ -9,7 +9,55 @@ module Clusters ...@@ -9,7 +9,55 @@ module Clusters
end end
def execute(cluster) def execute(cluster)
cluster.update(params) if validate_params(cluster)
cluster.update(params)
else
false
end
end
private
def can_admin_pipeline_for_project?(project)
Ability.allowed?(current_user, :admin_pipeline, project)
end
def validate_params(cluster)
if params[:management_project_id]
management_project = management_project_scope(cluster).find_by_id(params[:management_project_id])
unless management_project
cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action'))
return false
end
unless can_admin_pipeline_for_project?(management_project)
# Use same message as not found to prevent enumeration
cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action'))
return false
end
end
true
end
def management_project_scope(cluster)
return ::Project.all if cluster.instance_type?
group =
if cluster.group_type?
cluster.first_group
elsif cluster.project_type?
cluster.first_project&.namespace
end
# Prevent users from selecting nested projects until
# https://gitlab.com/gitlab-org/gitlab/issues/34650 is resolved
include_subgroups = cluster.group_type?
::GroupProjectsFinder.new(group: group, current_user: current_user, options: { only_owned: true, include_subgroups: include_subgroups }).execute
end end
end end
end end
...@@ -10,13 +10,20 @@ module MergeRequests ...@@ -10,13 +10,20 @@ module MergeRequests
# TODO: this should handle all quick actions that don't have side effects # TODO: this should handle all quick actions that don't have side effects
# https://gitlab.com/gitlab-org/gitlab-foss/issues/53658 # https://gitlab.com/gitlab-org/gitlab-foss/issues/53658
merge_quick_actions_into_params!(merge_request, only: [:target_branch]) merge_quick_actions_into_params!(merge_request, only: [:target_branch])
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) if params.has_key?(:force_remove_source_branch)
# Assign the projects first so we can use policies for `filter_params` # Assign the projects first so we can use policies for `filter_params`
merge_request.author = current_user merge_request.author = current_user
merge_request.source_project = find_source_project merge_request.source_project = find_source_project
merge_request.target_project = find_target_project merge_request.target_project = find_target_project
# Source project sets the default source branch removal setting
merge_request.merge_params['force_remove_source_branch'] =
if params.key?(:force_remove_source_branch)
params.delete(:force_remove_source_branch)
else
merge_request.source_project.remove_source_branch_after_merge?
end
filter_params(merge_request) filter_params(merge_request)
# merge_request.assign_attributes(...) below is a Rails # merge_request.assign_attributes(...) below is a Rails
......
...@@ -7,9 +7,9 @@ ...@@ -7,9 +7,9 @@
.form-check .form-check
= f.check_box :mirror_available, class: 'form-check-input' = f.check_box :mirror_available, class: 'form-check-input'
= f.label :mirror_available, class: 'form-check-label' do = f.label :mirror_available, class: 'form-check-label' do
= _('Allow mirrors to be set up for projects') = _('Allow repository mirroring to be configured by project maintainers')
%span.form-text.text-muted %span.form-text.text-muted
= _('If disabled, only admins will be able to set up mirrors in projects.') = _('If disabled, only admins will be able to configure repository mirroring.')
= link_to icon('question-circle'), help_page_path('workflow/repository_mirroring') = link_to icon('question-circle'), help_page_path('workflow/repository_mirroring')
= render_if_exists 'admin/application_settings/mirror_settings', form: f = render_if_exists 'admin/application_settings/mirror_settings', form: f
......
...@@ -29,4 +29,4 @@ ...@@ -29,4 +29,4 @@
= render 'third_party_offers', application_setting: @application_setting = render 'third_party_offers', application_setting: @application_setting
= render 'admin/application_settings/snowplow', expanded: expanded_by_default? = render 'admin/application_settings/snowplow', expanded: expanded_by_default?
= render 'admin/application_settings/pendo' = render_if_exists 'admin/application_settings/pendo'
...@@ -5,11 +5,11 @@ ...@@ -5,11 +5,11 @@
%section.settings.as-mirror.no-animate#js-mirror-settings{ class: ('expanded' if expanded_by_default?) } %section.settings.as-mirror.no-animate#js-mirror-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header .settings-header
%h4 %h4
= _('Repository mirror') = _('Repository mirroring')
%button.btn.js-settings-toggle{ type: 'button' } %button.btn.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? 'Collapse' : 'Expand' = expanded_by_default? ? 'Collapse' : 'Expand'
%p %p
= _('Configure push mirrors.') = _('Configure repository mirroring.')
.settings-content .settings-content
= render partial: 'repository_mirrors_form' = render partial: 'repository_mirrors_form'
......
- form_field = local_assigns.fetch(:form_field, nil)
- variable = local_assigns.fetch(:variable, nil)
- key = variable[0]
- value = variable[1]
- variable_type = variable[2] || "env_var"
- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
- variable_type_input_name = "#{form_field}[variables_attributes][][variable_type]"
- key_input_name = "#{form_field}[variables_attributes][][key]"
- value_input_name = "#{form_field}[variables_attributes][][secret_value]"
%li.js-row.ci-variable-row
.ci-variable-row-body.border-bottom
%input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
%select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control.custom-select.table-section.section-15{ name: variable_type_input_name }
= options_for_select(ci_variable_type_options, variable_type)
%input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control.table-section.section-15{ type: "text",
name: key_input_name,
value: key,
placeholder: s_('CiVariables|Input variable key') }
.ci-variable-body-item.gl-show-field-errors.table-section.section-15.border-top-0.p-0
%textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control{ rows: 1,
name: value_input_name,
placeholder: s_('CiVariables|Input variable value') }
= value
%button.js-row-remove-button.ci-variable-row-remove-button.table-section.section-5.border-top-0{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
= icon('minus-circle')
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= link_to new_project_path, class: "blank-state blank-state-link" do = link_to new_project_path, class: "blank-state blank-state-link" do
.blank-state-icon .blank-state-icon
= image_tag("illustrations/welcome/add_new_project") = custom_icon("add_new_project", size: 50)
.blank-state-body .blank-state-body
%h3.blank-state-title %h3.blank-state-title
Create a project Create a project
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
- if current_user.can_create_group? - if current_user.can_create_group?
= link_to new_group_path, class: "blank-state blank-state-link" do = link_to new_group_path, class: "blank-state blank-state-link" do
.blank-state-icon .blank-state-icon
= image_tag("illustrations/welcome/add_new_group") = custom_icon("add_new_group", size: 50)
.blank-state-body .blank-state-body
%h3.blank-state-title %h3.blank-state-title
Create a group Create a group
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
= link_to new_admin_user_path, class: "blank-state blank-state-link" do = link_to new_admin_user_path, class: "blank-state blank-state-link" do
.blank-state-icon .blank-state-icon
= image_tag("illustrations/welcome/add_new_user") = custom_icon("add_new_user", size: 50)
.blank-state-body .blank-state-body
%h3.blank-state-title %h3.blank-state-title
Add people Add people
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
= link_to admin_root_path, class: "blank-state blank-state-link" do = link_to admin_root_path, class: "blank-state blank-state-link" do
.blank-state-icon .blank-state-icon
= image_tag("illustrations/welcome/configure_server") = custom_icon("configure_server", size: 50)
.blank-state-body .blank-state-body
%h3.blank-state-title %h3.blank-state-title
Configure GitLab Configure GitLab
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to new_project_path, class: "blank-state blank-state-link" do = link_to new_project_path, class: "blank-state blank-state-link" do
.blank-state-icon .blank-state-icon
= image_tag("illustrations/welcome/add_new_project") = custom_icon("add_new_project", size: 50)
.blank-state-body .blank-state-body
%h3.blank-state-title %h3.blank-state-title
Create a project Create a project
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
- else - else
.blank-state .blank-state
.blank-state-icon .blank-state-icon
= image_tag("illustrations/welcome/add_new_project") = custom_icon("add_new_project", size: 50)
.blank-state-body .blank-state-body
%h3.blank-state-title %h3.blank-state-title
Create a project Create a project
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
- if current_user.can_create_group? - if current_user.can_create_group?
= link_to new_group_path, class: "blank-state blank-state-link" do = link_to new_group_path, class: "blank-state blank-state-link" do
.blank-state-icon .blank-state-icon
= image_tag("illustrations/welcome/add_new_group") = custom_icon("add_new_group", size: 50)
.blank-state-body .blank-state-body
%h3.blank-state-title %h3.blank-state-title
Create a group Create a group
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
- if public_project_count > 0 - if public_project_count > 0
= link_to trending_explore_projects_path, class: "blank-state blank-state-link" do = link_to trending_explore_projects_path, class: "blank-state blank-state-link" do
.blank-state-icon .blank-state-icon
= image_tag("illustrations/welcome/globe") = custom_icon("globe", size: 50)
.blank-state-body .blank-state-body
%h3.blank-state-title %h3.blank-state-title
Explore public projects Explore public projects
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
= link_to "https://docs.gitlab.com/", class: "blank-state blank-state-link" do = link_to "https://docs.gitlab.com/", class: "blank-state blank-state-link" do
.blank-state-icon .blank-state-icon
= image_tag("illustrations/welcome/lightbulb") = custom_icon("lightbulb", size: 50)
.blank-state-body .blank-state-body
%h3.blank-state-title %h3.blank-state-title
Learn more about GitLab Learn more about GitLab
......
...@@ -11,5 +11,5 @@ ...@@ -11,5 +11,5 @@
= form_tag search_path, method: :get, class: 'form-inline-flex' do |f| = form_tag search_path, method: :get, class: 'form-inline-flex' do |f|
.field .field
= search_field_tag :search, '', placeholder: _('Search for projects, issues, etc.'), class: 'form-control' = search_field_tag :search, '', placeholder: _('Search for projects, issues, etc.'), class: 'form-control'
= button_tag 'Search', class: 'btn btn-success', name: nil, type: 'submit' = button_tag _('Search'), class: 'btn btn-sm btn-success', name: nil, type: 'submit'
= render 'errors/footer' = render 'errors/footer'
...@@ -90,4 +90,4 @@ ...@@ -90,4 +90,4 @@
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
= render 'layouts/snowplow' = render 'layouts/snowplow'
= render 'layouts/pendo' = render_if_exists 'layouts/pendo'
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1; };p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","#{asset_url('snowplow/sp.js')}","snowplow")); n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","#{asset_url('snowplow/sp.js')}","snowplow"));
window.snowplowOptions = #{Gitlab::Tracking.snowplow_options(@group, current_user).to_json} window.snowplowOptions = #{Gitlab::Tracking.snowplow_options(@group).to_json}
...@@ -12,3 +12,9 @@ ...@@ -12,3 +12,9 @@
= form.check_box :printing_merge_request_link_enabled, class: 'form-check-input' = form.check_box :printing_merge_request_link_enabled, class: 'form-check-input'
= form.label :printing_merge_request_link_enabled, class: 'form-check-label' do = form.label :printing_merge_request_link_enabled, class: 'form-check-label' do
= s_('ProjectSettings|Show link to create/view merge request when pushing from the command line') = s_('ProjectSettings|Show link to create/view merge request when pushing from the command line')
.form-check.mb-2
= form.check_box :remove_source_branch_after_merge, class: 'form-check-input'
= form.label :remove_source_branch_after_merge, class: 'form-check-label' do
= s_("ProjectSettings|Enable 'Delete source branch' option by default")
.descr.text-secondary
= s_('ProjectSettings|Existing merge requests and protected branches are not affected')
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= author_avatar(commit, size: 36, has_tooltip: false) = author_avatar(commit, size: 36, has_tooltip: false)
.commit-row-title .commit-row-title
%span.item-title.str-truncated-100 %span.item-title.str-truncated-100
= link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title = link_to commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title
.float-right .float-right
= link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha" = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha"
&nbsp; &nbsp;
......
...@@ -8,8 +8,9 @@ ...@@ -8,8 +8,9 @@
.input-group-text .input-group-text
= s_("CompareBranches|Source") = s_("CompareBranches|Source")
= hidden_field_tag :to, params[:to] = hidden_field_tag :to, params[:to]
= button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip monospace", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do = button_tag type: 'button', title: params[:to], class: "btn form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
.dropdown-toggle-text.str-truncated= params[:to] || _("Select branch/tag") .dropdown-toggle-text.str-truncated.monospace.float-left= params[:to] || _("Select branch/tag")
= sprite_icon('arrow-down', size: 16, css_class: 'float-right')
= render 'shared/ref_dropdown' = render 'shared/ref_dropdown'
.compare-ellipsis.inline ... .compare-ellipsis.inline ...
.form-group.dropdown.compare-form-group.from.js-compare-from-dropdown .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown
...@@ -18,8 +19,9 @@ ...@@ -18,8 +19,9 @@
.input-group-text .input-group-text
= s_("CompareBranches|Target") = s_("CompareBranches|Target")
= hidden_field_tag :from, params[:from] = hidden_field_tag :from, params[:from]
= button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip monospace", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do = button_tag type: 'button', title: params[:from], class: "btn form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
.dropdown-toggle-text.str-truncated= params[:from] || _("Select branch/tag") .dropdown-toggle-text.str-truncated.monospace.float-left= params[:from] || _("Select branch/tag")
= sprite_icon('arrow-down', size: 16, css_class: 'float-right')
= render 'shared/ref_dropdown' = render 'shared/ref_dropdown'
&nbsp; &nbsp;
= button_tag s_("CompareBranches|Compare"), class: "btn btn-success commits-compare-btn" = button_tag s_("CompareBranches|Compare"), class: "btn btn-success commits-compare-btn"
......
...@@ -3,10 +3,12 @@ ...@@ -3,10 +3,12 @@
.form-group .form-group
= f.label :auth_method, _('Authentication method'), class: 'label-bold' = f.label :auth_method, _('Authentication method'), class: 'label-bold'
= f.select :auth_method, .select-wrapper
options_for_select(auth_options, mirror.auth_method), = f.select :auth_method,
{}, { class: "form-control js-mirror-auth-type qa-authentication-method" } options_for_select(auth_options, mirror.auth_method),
= f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type" {}, { class: "form-control select-control js-mirror-auth-type qa-authentication-method" }
= icon('chevron-down')
= f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type"
.form-group .form-group
.collapse.js-well-changing-auth .collapse.js-well-changing-auth
......
.form-group .form-group
= label_tag :mirror_direction, _('Mirror direction'), class: 'label-light' = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light'
= select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction qa-mirror-direction', disabled: true .select-wrapper
= select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control select-control js-mirror-direction qa-mirror-direction', disabled: true
= icon('chevron-down')
= render partial: "projects/mirrors/mirror_repos_push", locals: { f: f } = render partial: "projects/mirrors/mirror_repos_push", locals: { f: f }
...@@ -23,6 +23,13 @@ ...@@ -23,6 +23,13 @@
%label %label
= s_('Pipeline|Variables') = s_('Pipeline|Variables')
%ul.ci-variable-list %ul.ci-variable-list
- if params[:var]
- params[:var].each do |variable|
= render 'ci/variables/url_query_variable_row', form_field: 'pipeline', variable: variable
- if params[:file_var]
- params[:file_var].each do |variable|
- variable.push("file")
= render 'ci/variables/url_query_variable_row', form_field: 'pipeline', variable: variable
= render 'ci/variables/variable_row', form_field: 'pipeline', only_key_value: true = render 'ci/variables/variable_row', form_field: 'pipeline', only_key_value: true
.form-text.text-muted .form-text.text-muted
= (s_("Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default.") % {settings_link: settings_link}).html_safe = (s_("Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default.") % {settings_link: settings_link}).html_safe
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
%p.settings-message.text-center %p.settings-message.text-center
= s_("ProtectedBranch|There are currently no protected branches, protect a branch with the form above.") = s_("ProtectedBranch|There are currently no protected branches, protect a branch with the form above.")
- else - else
.flash-container
%table.table.table-bordered %table.table.table-bordered
%colgroup %colgroup
%col{ width: "20%" } %col{ width: "20%" }
...@@ -27,8 +28,6 @@ ...@@ -27,8 +28,6 @@
- if can_admin_project - if can_admin_project
%th %th
%tbody %tbody
%tr
%td.flash-container{ colspan: 5 }
= yield = yield
= paginate @protected_branches, theme: 'gitlab' = paginate @protected_branches, theme: 'gitlab'
...@@ -2,6 +2,6 @@ ...@@ -2,6 +2,6 @@
- return unless project - return unless project
- blob = parse_search_result(blob) - blob = parse_search_result(blob)
- blob_link = project_blob_path(project, tree_join(blob.ref, blob.filename)) - blob_link = project_blob_path(project, tree_join(blob.ref, blob.path))
= render partial: 'search/results/blob_data', locals: { blob: blob, project: project, file_name: blob.filename, blob_link: blob_link } = render partial: 'search/results/blob_data', locals: { blob: blob, project: project, path: blob.path, blob_link: blob_link }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= link_to blob_link do = link_to blob_link do
%i.fa.fa-file %i.fa.fa-file
%strong %strong
= search_blob_title(project, file_name) = search_blob_title(project, path)
- if blob.data - if blob.data
.file-content.code.term{ data: { qa_selector: 'file_text_content' } } .file-content.code.term{ data: { qa_selector: 'file_text_content' } }
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
- wiki_blob = parse_search_result(wiki_blob) - wiki_blob = parse_search_result(wiki_blob)
- wiki_blob_link = project_wiki_path(project, wiki_blob.basename) - wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: wiki_blob.filename, blob_link: wiki_blob_link } = render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, path: wiki_blob.path, blob_link: wiki_blob_link }
...@@ -7,3 +7,7 @@ ...@@ -7,3 +7,7 @@
= link_to _('Settings'), project_settings_ci_cd_path(project), class: 'alert-link' = link_to _('Settings'), project_settings_ci_cd_path(project), class: 'alert-link'
| |
= link_to _('Dismiss'), '#', class: 'hide-auto-devops-implicitly-enabled-banner alert-link', data: { project_id: project.id } = link_to _('Dismiss'), '#', class: 'hide-auto-devops-implicitly-enabled-banner alert-link', data: { project_id: project.id }
- unless Gitlab.config.registry.enabled
%div
= icon('exclamation-triangle')
= _('Container registry is not enabled on this GitLab instance. Ask an administrator to enable it in order for AutoDevOps to work.')
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
%a.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i } %a.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i }
= link_icon = link_icon
= i = i
.blob-content{ data: { blob_id: blob.id } } .blob-content.js-blob-content{ data: { blob_id: blob.id } }
%pre.code.highlight %pre.code.highlight
%code %code
= blob.present.highlight = blob.present.highlight
---
title: 'Geo: Add resigns-related fields to Geo Node Status table'
merge_request: 18379
author:
type: other
---
title: Close issues on Prometheus alert recovery
merge_request: 18431
author:
type: added
---
title: Populate new pipeline CI vars from params
merge_request: 19023
author:
type: added
---
title: Adding dropdown arrow icon and updated text alignment
merge_request:
author:
type: other
---
title: Change selects from default browser style to custom style
merge_request:
author:
type: other
---
title: Expose mergeable state of a merge request
merge_request: 18888
author: briankabiro
type: added
---
title: Remove pointer cursor from MemoryUsage chart on MR widget deployment
merge_request: 18599
author:
type: fixed
---
title: Fix "project or group was moved" alerts showing up in the wrong pages
merge_request: 18985
author:
type: fixed
---
title: Fix empty chart in collapsed sections
merge_request: 18699
author:
type: fixed
---
title: Removes arrow icons for old collapsible sections
merge_request:
author:
type: fixed
---
title: Show start and end dates in Epics list page
merge_request: 19006
author:
type: added
---
title: Bump Auto-Deploy image to v0.3.0
merge_request: 18809
author:
type: added
---
title: Add modsecurity template for ingress-controller
merge_request: 18485
author:
type: changed
title: Create table for elastic stack.
merge_request: 18015
author:
type: added
---
title: Allow adding requests to performance bar manually
merge_request: 18464
author:
type: other
---
title: Removes `export_designs` feature flag
merge_request: 18507
author: nate geslin
type: other
---
title: Adds ability to set management project for cluster via API
merge_request: 18429
author:
type: added
---
title: Drop `id` column from `ci_build_trace_sections` table
merge_request: 18741
author:
type: changed
---
title: Add extra sentence about registry to AutoDevOps popup
merge_request: 19092
author:
type: changed
---
title: Fix N+1 for group container repositories view
merge_request: 18979
author:
type: performance
---
title: Serialize short sha as nil if head commit is blank
merge_request: 19014
author:
type: fixed
---
title: Support Enable/Disable operations in Feature Flag API
merge_request: 18368
author:
type: added
---
title: Do not render links in commit message on blame page
merge_request: 19128
author:
type: performance
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment