Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue59
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js3
-rw-r--r--app/assets/javascripts/filtered_search/constants.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js2
-rw-r--r--app/assets/javascripts/filtered_search/visual_token_value.js3
-rw-r--r--app/assets/javascripts/ide/services/index.js8
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/actions.js1
-rw-r--r--app/assets/javascripts/ide/stores/utils.js11
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue53
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue11
-rw-r--r--app/assets/javascripts/merge_request_tabs.js10
-rw-r--r--app/assets/javascripts/mr_popover/queries/merge_request.graphql2
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue13
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue16
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue14
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue32
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue20
-rw-r--r--app/assets/stylesheets/bootstrap.scss37
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss7
-rw-r--r--app/assets/stylesheets/framework.scss3
-rw-r--r--app/assets/stylesheets/framework/forms.scss16
-rw-r--r--app/assets/stylesheets/pages/builds.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss3
-rw-r--r--app/assets/stylesheets/pages/settings.scss5
-rw-r--r--app/controllers/clusters/clusters_controller.rb2
-rw-r--r--app/controllers/concerns/issuable_collections.rb6
-rw-r--r--app/controllers/projects/registry/tags_controller.rb2
-rw-r--r--app/graphql/gitlab_schema.rb4
-rw-r--r--app/graphql/types/issue_type.rb4
-rw-r--r--app/graphql/types/merge_request_type.rb4
-rw-r--r--app/graphql/types/notes/diff_position_type.rb46
-rw-r--r--app/graphql/types/notes/discussion_type.rb15
-rw-r--r--app/graphql/types/notes/note_type.rb46
-rw-r--r--app/graphql/types/notes/noteable_type.rb25
-rw-r--r--app/graphql/types/notes/position_type_enum.rb13
-rw-r--r--app/graphql/types/permission_types/note.rb11
-rw-r--r--app/graphql/types/project_statistics_type.rb2
-rw-r--r--app/graphql/types/project_type.rb2
-rw-r--r--app/graphql/types/task_completion_status.rb11
-rw-r--r--app/helpers/services_helper.rb2
-rw-r--r--app/models/clusters/cluster.rb27
-rw-r--r--app/models/clusters/platforms/kubernetes.rb13
-rw-r--r--app/models/concerns/diff_positionable_note.rb9
-rw-r--r--app/models/discussion.rb8
-rw-r--r--app/models/lfs_object.rb2
-rw-r--r--app/models/lfs_objects_project.rb8
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project.rb20
-rw-r--r--app/models/project_services/data_fields.rb10
-rw-r--r--app/models/project_services/deployment_service.rb39
-rw-r--r--app/models/project_services/issue_tracker_data.rb25
-rw-r--r--app/models/project_services/jira_tracker_data.rb30
-rw-r--r--app/models/project_services/kubernetes_service.rb49
-rw-r--r--app/models/project_services/mock_deployment_service.rb16
-rw-r--r--app/models/service.rb5
-rw-r--r--app/models/user_callout_enums.rb5
-rw-r--r--app/policies/base_policy.rb4
-rw-r--r--app/policies/global_policy.rb4
-rw-r--r--app/policies/project_policy.rb5
-rw-r--r--app/policies/project_statistics_policy.rb5
-rw-r--r--app/serializers/pipeline_entity.rb1
-rw-r--r--app/services/files/create_service.rb2
-rw-r--r--app/services/files/multi_service.rb2
-rw-r--r--app/services/lfs/file_transformer.rb16
-rw-r--r--app/views/admin/application_settings/ci_cd.html.haml2
-rw-r--r--app/views/admin/services/_deprecated_message.html.haml3
-rw-r--r--app/views/admin/services/_form.html.haml5
-rw-r--r--app/views/admin/services/edit.html.haml3
-rw-r--r--app/views/clusters/clusters/gcp/_form.html.haml9
-rw-r--r--app/views/clusters/platforms/kubernetes/_form.html.haml2
-rw-r--r--app/views/groups/settings/_permissions.html.haml1
-rw-r--r--app/views/layouts/_head.html.haml1
-rw-r--r--app/views/projects/ci/builds/_build.html.haml5
-rw-r--r--app/views/projects/commit/_commit_box.html.haml2
-rw-r--r--app/views/projects/merge_requests/_mr_box.html.haml4
-rw-r--r--app/views/shared/projects/_search_form.html.haml2
79 files changed, 558 insertions, 333 deletions
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
deleted file mode 100644
index 6c256fa6736..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-<script>
-import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
-import iconCommit from '../svg/icon_commit.svg';
-import limitWarning from './limit_warning_component.vue';
-import totalTime from './total_time_component.vue';
-
-export default {
- components: {
- userAvatarImage,
- totalTime,
- limitWarning,
- },
- props: {
- items: {
- type: Array,
- default: () => [],
- },
- stage: {
- type: Object,
- default: () => ({}),
- },
- },
- computed: {
- iconCommit() {
- return iconCommit;
- },
- },
-};
-</script>
-<template>
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="(commit, i) in items" :key="i" class="stage-event-item">
- <div class="item-details item-conmmit-component">
- <!-- FIXME: Pass an alt attribute here for accessibility -->
- <user-avatar-image :img-src="commit.author.avatarUrl" />
- <h5 class="item-title commit-title">
- <a :href="commit.commitUrl"> {{ commit.title }} </a>
- </h5>
- <span>
- {{ s__('FirstPushedBy|First') }} <span class="commit-icon" v-html="iconCommit"> </span>
- <a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{
- commit.shortSha
- }}</a>
- {{ s__('FirstPushedBy|pushed by') }}
- <a :href="commit.author.webUrl" class="commit-author-link">
- {{ commit.author.name }}
- </a>
- </span>
- </div>
- <div class="item-time"><total-time :time="commit.totalTime" /></div>
- </li>
- </ul>
- </div>
-</template>
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 3f0a9f2602c..b56e08175cc 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -5,7 +5,6 @@ import Flash from '../flash';
import Translate from '../vue_shared/translate';
import banner from './components/banner.vue';
import stageCodeComponent from './components/stage_code_component.vue';
-import stagePlanComponent from './components/stage_plan_component.vue';
import stageComponent from './components/stage_component.vue';
import stageReviewComponent from './components/stage_review_component.vue';
import stageStagingComponent from './components/stage_staging_component.vue';
@@ -26,7 +25,7 @@ export default () => {
components: {
banner,
'stage-issue-component': stageComponent,
- 'stage-plan-component': stagePlanComponent,
+ 'stage-plan-component': stageComponent,
'stage-code-component': stageCodeComponent,
'stage-test-component': stageTestComponent,
'stage-review-component': stageReviewComponent,
diff --git a/app/assets/javascripts/filtered_search/constants.js b/app/assets/javascripts/filtered_search/constants.js
new file mode 100644
index 00000000000..8ca0e94cfeb
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/constants.js
@@ -0,0 +1,2 @@
+/* eslint-disable import/prefer-default-export */
+export const TOKEN_TYPES = ['author', 'assignee'];
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index 315cd6f64da..7f6457242ef 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -1,4 +1,4 @@
-import VisualTokenValue from 'ee_else_ce/filtered_search/visual_token_value';
+import VisualTokenValue from './visual_token_value';
import { objectToQueryString } from '~/lib/utils/common_utils';
import FilteredSearchContainer from './container';
diff --git a/app/assets/javascripts/filtered_search/visual_token_value.js b/app/assets/javascripts/filtered_search/visual_token_value.js
index a54b445fb0a..018207541b3 100644
--- a/app/assets/javascripts/filtered_search/visual_token_value.js
+++ b/app/assets/javascripts/filtered_search/visual_token_value.js
@@ -6,6 +6,7 @@ import DropdownUtils from '~/filtered_search/dropdown_utils';
import Flash from '~/flash';
import UsersCache from '~/lib/utils/users_cache';
import { __ } from '~/locale';
+import { TOKEN_TYPES } from 'ee_else_ce/filtered_search/constants';
export default class VisualTokenValue {
constructor(tokenValue, tokenType) {
@@ -22,7 +23,7 @@ export default class VisualTokenValue {
if (tokenType === 'label') {
this.updateLabelTokenColor(tokenValueContainer);
- } else if (tokenType === 'author' || tokenType === 'assignee') {
+ } else if (TOKEN_TYPES.includes(tokenType)) {
this.updateUserTokenAppearance(tokenValueContainer, tokenValueElement);
} else if (tokenType === 'my-reaction') {
this.updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement);
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index ba33b6826d6..840761f68db 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -56,7 +56,13 @@ export default {
return Api.branchSingle(projectId, currentBranchId);
},
commit(projectId, payload) {
- return Api.commitMultiple(projectId, payload);
+ // Currently the `commit` endpoint does not support `start_sha` so we
+ // have to make the request in the FE. This is not ideal and will be
+ // resolved soon. https://gitlab.com/gitlab-org/gitlab-ce/issues/59023
+ const { branch, start_sha: ref } = payload;
+ const branchPromise = ref ? Api.createBranch(projectId, { ref, branch }) : Promise.resolve();
+
+ return branchPromise.then(() => Api.commitMultiple(projectId, payload));
},
getFiles(projectUrl, branchId) {
const url = `${projectUrl}/files/${branchId}`;
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
index 51062f092ad..ff1255ce749 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -142,6 +142,7 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
getters,
state,
rootState,
+ rootGetters,
});
return service.commit(rootState.currentProjectId, payload);
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index bcc9ca60d9b..4e7a8765abe 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -135,7 +135,14 @@ export const getCommitFiles = stagedFiles =>
});
}, []);
-export const createCommitPayload = ({ branch, getters, newBranch, state, rootState }) => ({
+export const createCommitPayload = ({
+ branch,
+ getters,
+ newBranch,
+ state,
+ rootState,
+ rootGetters,
+}) => ({
branch,
commit_message: state.commitMessage || getters.preBuiltCommitMessage,
actions: getCommitFiles(rootState.stagedFiles).map(f => ({
@@ -146,7 +153,7 @@ export const createCommitPayload = ({ branch, getters, newBranch, state, rootSta
encoding: f.base64 ? 'base64' : 'text',
last_commit_id: newBranch || f.deleted || f.prevPath ? undefined : f.lastCommitSha,
})),
- start_branch: newBranch ? rootState.currentBranchId : undefined,
+ start_sha: newBranch ? rootGetters.lastCommit.short_id : undefined,
});
export const createNewMergeRequestUrl = (projectUrl, source, target) =>
diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue
index 92e20e92d66..d611b370ab9 100644
--- a/app/assets/javascripts/jobs/components/job_log.vue
+++ b/app/assets/javascripts/jobs/components/job_log.vue
@@ -17,10 +17,19 @@ export default {
...mapState(['isScrolledToBottomBeforeReceivingTrace']),
},
updated() {
- this.$nextTick(() => this.handleScrollDown());
+ this.$nextTick(() => {
+ this.handleScrollDown();
+ this.handleCollapsibleRows();
+ });
},
mounted() {
- this.$nextTick(() => this.handleScrollDown());
+ this.$nextTick(() => {
+ this.handleScrollDown();
+ this.handleCollapsibleRows();
+ });
+ },
+ destroyed() {
+ this.removeEventListener();
},
methods: {
...mapActions(['scrollBottom']),
@@ -38,21 +47,45 @@ export default {
}, 0);
}
},
+ removeEventListener() {
+ this.$el
+ .querySelectorAll('.js-section-start')
+ .forEach(el => el.removeEventListener('click', this.handleSectionClick));
+ },
+ /**
+ * The collapsible rows are sent in HTML from the backend
+ * We need tos add a onclick handler for the divs that match `.js-section-start`
+ *
+ */
+ handleCollapsibleRows() {
+ this.$el
+ .querySelectorAll('.js-section-start')
+ .forEach(el => el.addEventListener('click', this.handleSectionClick));
+ },
+ /**
+ * On click, we toggle the hidden class of
+ * all the rows that match the `data-section` selector
+ */
+ handleSectionClick(evt) {
+ const clickedArrow = evt.currentTarget;
+ // toggle the arrow class
+ clickedArrow.classList.toggle('fa-caret-right');
+ clickedArrow.classList.toggle('fa-caret-down');
+
+ const { section } = clickedArrow.dataset;
+ const sibilings = this.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`);
+
+ sibilings.forEach(row => row.classList.toggle('hidden'));
+ },
},
};
</script>
<template>
<pre class="js-build-trace build-trace qa-build-trace">
- <code
- class="bash"
- v-html="trace"
- >
+ <code class="bash" v-html="trace">
</code>
- <div
- v-if="!isComplete"
- class="js-log-animation build-loader-animation"
- >
+ <div v-if="!isComplete" class="js-log-animation build-loader-animation">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index cb073a9b04d..6e92b599b0a 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -2,7 +2,6 @@
import _ from 'underscore';
import { GlLink } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
-import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue';
import Icon from '~/vue_shared/components/icon.vue';
export default {
@@ -10,7 +9,6 @@ export default {
CiIcon,
Icon,
GlLink,
- PipelineLink,
},
props: {
pipeline: {
@@ -50,12 +48,9 @@ export default {
<ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
<span class="font-weight-bold">{{ s__('Job|Pipeline') }}</span>
- <pipeline-link
- :href="pipeline.path"
- :pipeline-id="pipeline.id"
- :pipeline-iid="pipeline.iid"
- class="js-pipeline-path link-commit qa-pipeline-path"
- />
+ <gl-link :href="pipeline.path" class="js-pipeline-path link-commit qa-pipeline-path"
+ >#{{ pipeline.id }}</gl-link
+ >
<template v-if="hasRef">
{{ s__('Job|for') }}
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index e5cf43e8289..b6868e63716 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -147,14 +147,14 @@ export default class MergeRequestTabs {
e.stopImmediatePropagation();
e.preventDefault();
- const { action } = e.currentTarget.dataset;
+ const { action } = e.currentTarget.dataset || {};
- if (action) {
- const href = e.currentTarget.getAttribute('href');
- this.tabShown(action, href);
- } else if (isMetaClick(e)) {
+ if (isMetaClick(e)) {
const targetLink = e.currentTarget.getAttribute('href');
window.open(targetLink, '_blank');
+ } else if (action) {
+ const href = e.currentTarget.getAttribute('href');
+ this.tabShown(action, href);
}
}
}
diff --git a/app/assets/javascripts/mr_popover/queries/merge_request.graphql b/app/assets/javascripts/mr_popover/queries/merge_request.graphql
index 0bb9bc03bc7..37d4bc88a69 100644
--- a/app/assets/javascripts/mr_popover/queries/merge_request.graphql
+++ b/app/assets/javascripts/mr_popover/queries/merge_request.graphql
@@ -1,4 +1,4 @@
-query mergeRequest($projectPath: ID!, $mergeRequestIID: ID!) {
+query mergeRequest($projectPath: ID!, $mergeRequestIID: String!) {
project(fullPath: $projectPath) {
mergeRequest(iid: $mergeRequestIID) {
createdAt
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index f3a71ee434c..b2e365e5cde 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -83,8 +83,6 @@ export default {
v-if="shouldRenderContent"
:status="status"
:item-id="pipeline.id"
- :item-iid="pipeline.iid"
- :item-id-tooltip="__('Pipeline ID (IID)')"
:time="pipeline.created_at"
:user="pipeline.user"
:actions="actions"
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 00c02e15562..c41ecab1294 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -2,7 +2,6 @@
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import _ from 'underscore';
import { __, sprintf } from '~/locale';
-import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import popover from '~/vue_shared/directives/popover';
@@ -20,7 +19,6 @@ export default {
components: {
UserAvatarLink,
GlLink,
- PipelineLink,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -61,13 +59,10 @@ export default {
};
</script>
<template>
- <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags section-wrap">
- <pipeline-link
- :href="pipeline.path"
- :pipeline-id="pipeline.id"
- :pipeline-iid="pipeline.iid"
- class="js-pipeline-url-link"
- />
+ <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags">
+ <gl-link :href="pipeline.path" class="js-pipeline-url-link">
+ <span class="pipeline-id">#{{ pipeline.id }}</span>
+ </gl-link>
<div class="label-container">
<span
v-if="pipeline.flags.latest"
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index 1e4dfe76b26..f535b2ae9f2 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -64,7 +64,7 @@ export default {
<th>{{ s__('ContainerRegistry|Tag') }}</th>
<th>{{ s__('ContainerRegistry|Tag ID') }}</th>
<th>{{ s__('ContainerRegistry|Size') }}</th>
- <th>{{ s__('ContainerRegistry|Created') }}</th>
+ <th>{{ s__('ContainerRegistry|Last Updated') }}</th>
<th></th>
</tr>
</thead>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index abe5bdd2901..34cdb70ce14 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -49,6 +49,7 @@ export default {
required: false,
default: () => ({
sourceProjectId: '',
+ sourceProjectPath: '',
mergeRequestId: '',
appUrl: '',
}),
@@ -184,11 +185,6 @@ export default {
:link="deploymentExternalUrl"
:css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
/>
- <visual-review-app-link
- v-if="showVisualReviewApp"
- :link="deploymentExternalUrl"
- :app-metadata="visualReviewAppMeta"
- />
</template>
<template slot="result" slot-scope="slotProps">
@@ -213,12 +209,12 @@ export default {
:link="deploymentExternalUrl"
css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline"
/>
- <visual-review-app-link
- v-if="showVisualReviewApp"
- :link="deploymentExternalUrl"
- :app-metadata="visualReviewAppMeta"
- />
</template>
+ <visual-review-app-link
+ v-if="showVisualReviewApp"
+ :link="deploymentExternalUrl"
+ :app-metadata="visualReviewAppMeta"
+ />
</template>
<span
v-if="deployment.stop_url"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index c377c16fb13..f5fa68308bc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -5,7 +5,6 @@ import { sprintf, __ } from '~/locale';
import PipelineStage from '~/pipelines/components/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
-import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline';
@@ -17,7 +16,6 @@ export default {
Icon,
TooltipOnTruncate,
GlLink,
- PipelineLink,
LinkedPipelinesMiniList: () =>
import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
},
@@ -114,12 +112,9 @@ export default {
<div class="media-body">
<div class="font-weight-bold js-pipeline-info-container">
{{ s__('Pipeline|Pipeline') }}
- <pipeline-link
- :href="pipeline.path"
- :pipeline-id="pipeline.id"
- :pipeline-iid="pipeline.iid"
- class="pipeline-id pipeline-iid font-weight-normal"
- />
+ <gl-link :href="pipeline.path" class="pipeline-id font-weight-normal pipeline-number"
+ >#{{ pipeline.id }}</gl-link
+ >
{{ pipeline.details.status.label }}
<template v-if="hasCommitInfo">
{{ s__('Pipeline|for') }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
index 03a15ba81ed..17ac8ada32d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
@@ -1,4 +1,5 @@
<script>
+import _ from 'underscore';
import Deployment from './deployment.vue';
import MrWidgetContainer from './mr_widget_container.vue';
import MrWidgetPipeline from './mr_widget_pipeline.vue';
@@ -17,6 +18,8 @@ export default {
Deployment,
MrWidgetContainer,
MrWidgetPipeline,
+ MergeTrainInfo: () =>
+ import('ee_component/vue_merge_request_widget/components/merge_train_info.vue'),
},
props: {
mr: {
@@ -50,6 +53,7 @@ export default {
appUrl: this.mr.appUrl,
mergeRequestId: this.mr.iid,
sourceProjectId: this.mr.sourceProjectId,
+ sourceProjectPath: this.mr.sourceProjectFullPath,
};
},
pipeline() {
@@ -58,6 +62,9 @@ export default {
showVisualReviewAppLink() {
return Boolean(this.mr.visualReviewFF && this.mr.visualReviewAppAvailable);
},
+ showMergeTrainInfo() {
+ return _.isNumber(this.mr.mergeTrainIndex);
+ },
},
};
</script>
@@ -79,10 +86,15 @@ export default {
:class="deploymentClass"
:deployment="deployment"
:show-metrics="hasDeploymentMetrics"
- :show-visual-review-app="true"
+ :show-visual-review-app="showVisualReviewAppLink"
:visual-review-app-meta="visualReviewAppMeta"
/>
</div>
+ <merge-train-info
+ v-if="showMergeTrainInfo"
+ class="mr-widget-extension"
+ :merge-train-index="mr.mergeTrainIndex"
+ />
</template>
</mr-widget-container>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue b/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue
deleted file mode 100644
index eae4c06467c..00000000000
--- a/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-<script>
-import { GlLink, GlTooltipDirective } from '@gitlab/ui';
-
-export default {
- components: {
- GlLink,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- href: {
- type: String,
- required: true,
- },
- pipelineId: {
- type: Number,
- required: true,
- },
- pipelineIid: {
- type: Number,
- required: true,
- },
- },
-};
-</script>
-<template>
- <gl-link v-gl-tooltip :href="href" :title="__('Pipeline ID (IID)')">
- <span class="pipeline-id">#{{ pipelineId }}</span>
- <span class="pipeline-iid">(#{{ pipelineIid }})</span>
- </gl-link>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index 0bac63b1062..3f45dc7853b 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -37,16 +37,6 @@ export default {
type: Number,
required: true,
},
- itemIid: {
- type: Number,
- required: false,
- default: null,
- },
- itemIdTooltip: {
- type: String,
- required: false,
- default: '',
- },
time: {
type: String,
required: true,
@@ -95,12 +85,7 @@ export default {
<section class="header-main-content">
<ci-icon-badge :status="status" />
- <strong v-gl-tooltip :title="itemIdTooltip">
- {{ itemName }} #{{ itemId }}
- <template v-if="itemIid"
- >(#{{ itemIid }})</template
- >
- </strong>
+ <strong> {{ itemName }} #{{ itemId }} </strong>
<template v-if="shouldRenderTriggeredLabel">
triggered
@@ -111,8 +96,9 @@ export default {
<timeago-tooltip :time="time" />
+ by
+
<template v-if="user">
- by
<gl-link
v-gl-tooltip
:href="user.path"
diff --git a/app/assets/stylesheets/bootstrap.scss b/app/assets/stylesheets/bootstrap.scss
deleted file mode 100644
index 4a09da3d580..00000000000
--- a/app/assets/stylesheets/bootstrap.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Includes specific styles from the bootstrap4 folder in node_modules
- */
-
-@import "../../../node_modules/bootstrap/scss/functions";
-@import "../../../node_modules/bootstrap/scss/variables";
-@import "../../../node_modules/bootstrap/scss/mixins";
-@import "../../../node_modules/bootstrap/scss/root";
-@import "../../../node_modules/bootstrap/scss/reboot";
-@import "../../../node_modules/bootstrap/scss/type";
-@import "../../../node_modules/bootstrap/scss/images";
-@import "../../../node_modules/bootstrap/scss/code";
-@import "../../../node_modules/bootstrap/scss/grid";
-@import "../../../node_modules/bootstrap/scss/tables";
-@import "../../../node_modules/bootstrap/scss/forms";
-@import "../../../node_modules/bootstrap/scss/buttons";
-@import "../../../node_modules/bootstrap/scss/transitions";
-@import "../../../node_modules/bootstrap/scss/dropdown";
-@import "../../../node_modules/bootstrap/scss/button-group";
-@import "../../../node_modules/bootstrap/scss/input-group";
-@import "../../../node_modules/bootstrap/scss/custom-forms";
-@import "../../../node_modules/bootstrap/scss/nav";
-@import "../../../node_modules/bootstrap/scss/navbar";
-@import "../../../node_modules/bootstrap/scss/card";
-@import "../../../node_modules/bootstrap/scss/breadcrumb";
-@import "../../../node_modules/bootstrap/scss/pagination";
-@import "../../../node_modules/bootstrap/scss/badge";
-@import "../../../node_modules/bootstrap/scss/alert";
-@import "../../../node_modules/bootstrap/scss/progress";
-@import "../../../node_modules/bootstrap/scss/media";
-@import "../../../node_modules/bootstrap/scss/list-group";
-@import "../../../node_modules/bootstrap/scss/close";
-@import "../../../node_modules/bootstrap/scss/modal";
-@import "../../../node_modules/bootstrap/scss/tooltip";
-@import "../../../node_modules/bootstrap/scss/popover";
-@import "../../../node_modules/bootstrap/scss/utilities";
-@import "../../../node_modules/bootstrap/scss/print";
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index 802d58779d0..29473da21cc 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -1,7 +1,3 @@
-/*
- * Scss to help with bootstrap 3 to 4 migration
- */
-
$text-color: $gl-text-color;
$brand-primary: $blue-500;
@@ -18,6 +14,9 @@ $input-border: $border-color;
$padding-base-vertical: $gl-vert-padding;
$padding-base-horizontal: $gl-padding;
+/*
+ * Scss to help with bootstrap 3 to 4 migration
+ */
body,
.form-control,
.search form {
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 9b0d19b0ef0..14f4652e847 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -2,7 +2,8 @@
@import 'framework/variables_overrides';
@import 'framework/mixins';
-@import 'bootstrap';
+@import '../../../node_modules/@gitlab/ui/scss/gitlab_ui';
+
@import 'bootstrap_migration';
@import 'framework/layout';
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 2a601afff53..821e6691fe4 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -248,14 +248,24 @@ label {
.gl-form-checkbox {
align-items: baseline;
+ margin-right: 1rem;
+ margin-bottom: 0.25rem;
+
+ .form-check-input {
+ margin-right: 0;
+ }
+
+ .form-check-label {
+ padding-left: $gl-padding-8;
+ }
&.form-check-inline .form-check-input {
align-self: flex-start;
- margin-right: $gl-padding-8;
height: 1.5 * $gl-font-size;
}
- .help-text {
- margin-bottom: 0;
+ .form-check-input:disabled,
+ .form-check-input:disabled ~ .form-check-label {
+ cursor: not-allowed;
}
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 6fc742871e7..6e98908eeed 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -124,6 +124,10 @@
float: left;
padding-left: $gl-padding-8;
}
+
+ .section-header ~ .section.line {
+ margin-left: $gl-padding;
+ }
}
.build-header {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 8cb3fab74e0..3917937f4af 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -904,7 +904,8 @@
margin-right: -5px;
}
-.deploy-heading {
+.deploy-heading,
+.merge-train-info {
@include media-breakpoint-up(md) {
padding: $gl-padding-8 $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 0a9c56f5625..3b62121eb0d 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -340,6 +340,11 @@
.deprecated-service {
cursor: default;
+
+ a {
+ font-weight: $gl-font-weight-bold;
+ color: $white-light;
+ }
}
.personal-access-tokens-never-expires-label {
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 80ee7c35906..ec8077d18e3 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -128,6 +128,7 @@ class Clusters::ClustersController < Clusters::BaseController
:enabled,
:name,
:environment_scope,
+ :managed,
:base_domain,
platform_kubernetes_attributes: [
:api_url,
@@ -140,6 +141,7 @@ class Clusters::ClustersController < Clusters::BaseController
params.require(:cluster).permit(
:enabled,
:environment_scope,
+ :managed,
:base_domain,
platform_kubernetes_attributes: [
:namespace
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 9cf25915e92..88a0690938a 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -104,6 +104,12 @@ module IssuableCollections
# Used by view to highlight active option
@sort = options[:sort]
+ # When a user looks for an exact iid, we do not filter by search but only by iid
+ if params[:search] =~ /^#(?<iid>\d+)\z/
+ options[:iids] = Regexp.last_match[:iid]
+ params[:search] = nil
+ end
+
if @project
options[:project_id] = @project.id
options[:attempt_project_search_optimizations] = true
diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb
index 567d750caae..bf1d8d8b5fc 100644
--- a/app/controllers/projects/registry/tags_controller.rb
+++ b/app/controllers/projects/registry/tags_controller.rb
@@ -3,7 +3,7 @@
module Projects
module Registry
class TagsController < ::Projects::Registry::ApplicationController
- before_action :authorize_update_container_image!, only: [:destroy]
+ before_action :authorize_destroy_container_image!, only: [:destroy]
def index
respond_to do |format|
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 2e5bdbd79c8..5615909c4ec 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -7,8 +7,8 @@ class GitlabSchema < GraphQL::Schema
AUTHENTICATED_COMPLEXITY = 250
ADMIN_COMPLEXITY = 300
- DEFAULT_MAX_DEPTH = 10
- AUTHENTICATED_MAX_DEPTH = 15
+ DEFAULT_MAX_DEPTH = 15
+ AUTHENTICATED_MAX_DEPTH = 20
use BatchLoader::GraphQL
use Gitlab::Graphql::Authorize
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index dd5133189dc..f2365499eee 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -4,6 +4,8 @@ module Types
class IssueType < BaseObject
graphql_name 'Issue'
+ implements(Types::Notes::NoteableType)
+
authorize :read_issue
expose_permissions Types::PermissionTypes::Issue
@@ -49,5 +51,7 @@ module Types
field :created_at, Types::TimeType, null: false
field :updated_at, Types::TimeType, null: false
+
+ field :task_completion_status, Types::TaskCompletionStatus, null: false
end
end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 85ac3102442..dac4c24cf10 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -4,6 +4,8 @@ module Types
class MergeRequestType < BaseObject
graphql_name 'MergeRequest'
+ implements(Types::Notes::NoteableType)
+
authorize :read_merge_request
expose_permissions Types::PermissionTypes::MergeRequest
@@ -53,5 +55,7 @@ module Types
field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline
field :pipelines, Types::Ci::PipelineType.connection_type,
resolver: Resolvers::MergeRequestPipelinesResolver
+
+ field :task_completion_status, Types::TaskCompletionStatus, null: false
end
end
diff --git a/app/graphql/types/notes/diff_position_type.rb b/app/graphql/types/notes/diff_position_type.rb
new file mode 100644
index 00000000000..104ccb79bbb
--- /dev/null
+++ b/app/graphql/types/notes/diff_position_type.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ class DiffPositionType < BaseObject
+ graphql_name 'DiffPosition'
+
+ field :head_sha, GraphQL::STRING_TYPE, null: false,
+ description: "The sha of the head at the time the comment was made"
+ field :base_sha, GraphQL::STRING_TYPE, null: true,
+ description: "The merge base of the branch the comment was made on"
+ field :start_sha, GraphQL::STRING_TYPE, null: false,
+ description: "The sha of the branch being compared against"
+
+ field :file_path, GraphQL::STRING_TYPE, null: false,
+ description: "The path of the file that was changed"
+ field :old_path, GraphQL::STRING_TYPE, null: true,
+ description: "The path of the file on the start sha."
+ field :new_path, GraphQL::STRING_TYPE, null: true,
+ description: "The path of the file on the head sha."
+ field :position_type, Types::Notes::PositionTypeEnum, null: false
+
+ # Fields for text positions
+ field :old_line, GraphQL::INT_TYPE, null: true,
+ description: "The line on start sha that was changed",
+ resolve: -> (position, _args, _ctx) { position.old_line if position.on_text? }
+ field :new_line, GraphQL::INT_TYPE, null: true,
+ description: "The line on head sha that was changed",
+ resolve: -> (position, _args, _ctx) { position.new_line if position.on_text? }
+
+ # Fields for image positions
+ field :x, GraphQL::INT_TYPE, null: true,
+ description: "The X postion on which the comment was made",
+ resolve: -> (position, _args, _ctx) { position.x if position.on_image? }
+ field :y, GraphQL::INT_TYPE, null: true,
+ description: "The Y position on which the comment was made",
+ resolve: -> (position, _args, _ctx) { position.y if position.on_image? }
+ field :width, GraphQL::INT_TYPE, null: true,
+ description: "The total width of the image",
+ resolve: -> (position, _args, _ctx) { position.width if position.on_image? }
+ field :height, GraphQL::INT_TYPE, null: true,
+ description: "The total height of the image",
+ resolve: -> (position, _args, _ctx) { position.height if position.on_image? }
+ end
+ end
+end
diff --git a/app/graphql/types/notes/discussion_type.rb b/app/graphql/types/notes/discussion_type.rb
new file mode 100644
index 00000000000..c4691942f2d
--- /dev/null
+++ b/app/graphql/types/notes/discussion_type.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ class DiscussionType < BaseObject
+ graphql_name 'Discussion'
+
+ authorize :read_note
+
+ field :id, GraphQL::ID_TYPE, null: false
+ field :created_at, Types::TimeType, null: false
+ field :notes, Types::Notes::NoteType.connection_type, null: false, description: "All notes in the discussion"
+ end
+ end
+end
diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb
new file mode 100644
index 00000000000..85c55d16ac2
--- /dev/null
+++ b/app/graphql/types/notes/note_type.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ class NoteType < BaseObject
+ graphql_name 'Note'
+
+ authorize :read_note
+
+ expose_permissions Types::PermissionTypes::Note
+
+ field :id, GraphQL::ID_TYPE, null: false
+
+ field :project, Types::ProjectType,
+ null: true,
+ description: "The project this note is associated to",
+ resolve: -> (note, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, note.project_id).find }
+
+ field :author, Types::UserType,
+ null: false,
+ description: "The user who wrote this note",
+ resolve: -> (note, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, note.author_id).find }
+
+ field :resolved_by, Types::UserType,
+ null: true,
+ description: "The user that resolved the discussion",
+ resolve: -> (note, _args, _context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, note.resolved_by_id).find }
+
+ field :system, GraphQL::BOOLEAN_TYPE,
+ null: false,
+ description: "Whether or not this note was created by the system or by a user"
+
+ field :body, GraphQL::STRING_TYPE,
+ null: false,
+ method: :note,
+ description: "The content note itself"
+
+ field :created_at, Types::TimeType, null: false
+ field :updated_at, Types::TimeType, null: false
+ field :discussion, Types::Notes::DiscussionType, null: true, description: "The discussion this note is a part of"
+ field :resolvable, GraphQL::BOOLEAN_TYPE, null: false, method: :resolvable?
+ field :resolved_at, Types::TimeType, null: true, description: "The time the discussion was resolved"
+ field :position, Types::Notes::DiffPositionType, null: true, description: "The position of this note on a diff"
+ end
+ end
+end
diff --git a/app/graphql/types/notes/noteable_type.rb b/app/graphql/types/notes/noteable_type.rb
new file mode 100644
index 00000000000..9f126d67b0d
--- /dev/null
+++ b/app/graphql/types/notes/noteable_type.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ module NoteableType
+ include Types::BaseInterface
+
+ field :notes, Types::Notes::NoteType.connection_type, null: false, description: "All notes on this noteable"
+ field :discussions, Types::Notes::DiscussionType.connection_type, null: false, description: "All discussions on this noteable"
+
+ definition_methods do
+ def resolve_type(object, context)
+ case object
+ when Issue
+ Types::IssueType
+ when MergeRequest
+ Types::MergeRequestType
+ else
+ raise "Unknown GraphQL type for #{object}"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/notes/position_type_enum.rb b/app/graphql/types/notes/position_type_enum.rb
new file mode 100644
index 00000000000..abdb2cfc804
--- /dev/null
+++ b/app/graphql/types/notes/position_type_enum.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ class PositionTypeEnum < BaseEnum
+ graphql_name 'DiffPositionType'
+ description 'Type of file the position refers to'
+
+ value 'text'
+ value 'image'
+ end
+ end
+end
diff --git a/app/graphql/types/permission_types/note.rb b/app/graphql/types/permission_types/note.rb
new file mode 100644
index 00000000000..a585d3daaa8
--- /dev/null
+++ b/app/graphql/types/permission_types/note.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ module PermissionTypes
+ class Note < BasePermissionType
+ graphql_name 'NotePermissions'
+
+ abilities :read_note, :create_note, :admin_note, :resolve_note, :award_emoji
+ end
+ end
+end
diff --git a/app/graphql/types/project_statistics_type.rb b/app/graphql/types/project_statistics_type.rb
index 62537361918..4000c6db280 100644
--- a/app/graphql/types/project_statistics_type.rb
+++ b/app/graphql/types/project_statistics_type.rb
@@ -4,6 +4,8 @@ module Types
class ProjectStatisticsType < BaseObject
graphql_name 'ProjectStatistics'
+ authorize :read_statistics
+
field :commit_count, GraphQL::INT_TYPE, null: false
field :storage_size, GraphQL::INT_TYPE, null: false
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 2236ffa394d..81914b70c7f 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -70,7 +70,7 @@ module Types
field :group, Types::GroupType, null: true
field :statistics, Types::ProjectStatisticsType,
- null: false,
+ null: true,
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(obj.id).find }
field :repository, Types::RepositoryType, null: false
diff --git a/app/graphql/types/task_completion_status.rb b/app/graphql/types/task_completion_status.rb
new file mode 100644
index 00000000000..c289802509d
--- /dev/null
+++ b/app/graphql/types/task_completion_status.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ class TaskCompletionStatus < BaseObject
+ graphql_name 'TaskCompletionStatus'
+ description 'Completion status of tasks'
+
+ field :count, GraphQL::INT_TYPE, null: false
+ field :completed_count, GraphQL::INT_TYPE, null: false
+ end
+end
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index d4b50b7ecfb..01ccf163b45 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -39,7 +39,7 @@ module ServicesHelper
end
def disable_fields_service?(service)
- !current_controller?("admin/services") && service.deprecated?
+ service.is_a?(KubernetesService) || (!current_controller?("admin/services") && service.deprecated?)
end
extend self
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index ccc877fb924..8c044c86c47 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -193,21 +193,40 @@ module Clusters
platform_kubernetes.kubeclient if kubernetes?
end
+ ##
+ # This is subtly different to #find_or_initialize_kubernetes_namespace_for_project
+ # below because it will ignore any namespaces that have not got a service account
+ # token. This provides a guarantee that any namespace selected here can be used
+ # for cluster operations - a namespace needs to have a service account configured
+ # before it it can be used.
+ #
+ # This is used for selecting a namespace to use when querying a cluster, or
+ # generating variables to pass to CI.
def kubernetes_namespace_for(project)
- find_or_initialize_kubernetes_namespace_for_project(project).namespace
+ find_or_initialize_kubernetes_namespace_for_project(
+ project, scope: kubernetes_namespaces.has_service_account_token
+ ).namespace
end
- def find_or_initialize_kubernetes_namespace_for_project(project)
+ ##
+ # This is subtly different to #kubernetes_namespace_for because it will include
+ # namespaces that have yet to receive a service account token. This allows
+ # the namespace configuration process to be repeatable - if a namespace has
+ # already been created without a token we don't need to create another
+ # record entirely, just set the token on the pre-existing namespace.
+ #
+ # This is used for configuring cluster namespaces.
+ def find_or_initialize_kubernetes_namespace_for_project(project, scope: kubernetes_namespaces)
attributes = { project: project }
attributes[:cluster_project] = cluster_project if project_type?
- kubernetes_namespaces.find_or_initialize_by(attributes).tap do |namespace|
+ scope.find_or_initialize_by(attributes).tap do |namespace|
namespace.set_defaults
end
end
def allow_user_defined_namespace?
- project_type?
+ project_type? || !managed?
end
def kube_ingress_domain
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 8e06156c73d..272861cacf0 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -80,9 +80,18 @@ module Clusters
.append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
end
- if kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project)
+ if !cluster.managed?
+ project_namespace = namespace.presence || "#{project.path}-#{project.id}".downcase
+
+ variables
+ .append(key: 'KUBE_URL', value: api_url)
+ .append(key: 'KUBE_TOKEN', value: token, public: false, masked: true)
+ .append(key: 'KUBE_NAMESPACE', value: project_namespace)
+ .append(key: 'KUBECONFIG', value: kubeconfig(project_namespace), public: false, file: true)
+
+ elsif kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project)
variables.concat(kubernetes_namespace.predefined_variables)
- elsif cluster.project_type? || !cluster.managed?
+ elsif cluster.project_type?
# As of 11.11 a user can create a cluster that they manage themselves,
# which replicates the existing project-level cluster behaviour.
# Once we have marked all project-level clusters that make use of this
diff --git a/app/models/concerns/diff_positionable_note.rb b/app/models/concerns/diff_positionable_note.rb
index b61bf29e6ad..2d09eff0111 100644
--- a/app/models/concerns/diff_positionable_note.rb
+++ b/app/models/concerns/diff_positionable_note.rb
@@ -3,6 +3,7 @@ module DiffPositionableNote
extend ActiveSupport::Concern
included do
+ delegate :on_text?, :on_image?, to: :position, allow_nil: true
before_validation :set_original_position, on: :create
before_validation :update_position, on: :create, if: :on_text?
@@ -28,14 +29,6 @@ module DiffPositionableNote
end
end
- def on_text?
- position&.position_type == "text"
- end
-
- def on_image?
- position&.position_type == "image"
- end
-
def supported?
for_commit? || self.noteable.has_complete_diff_refs?
end
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index 32529ebf71d..ae13cdfd85f 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -4,6 +4,7 @@
#
# A discussion of this type can be resolvable.
class Discussion
+ include GlobalID::Identification
include ResolvableDiscussion
attr_reader :notes, :context_noteable
@@ -11,14 +12,19 @@ class Discussion
delegate :created_at,
:project,
:author,
-
:noteable,
:commit_id,
:for_commit?,
:for_merge_request?,
+ :to_ability_name,
+ :editable?,
to: :first_note
+ def declarative_policy_delegate
+ first_note
+ end
+
def project_id
project&.id
end
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 5245dbc8d15..79a376ff0fd 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -5,7 +5,7 @@ class LfsObject < ApplicationRecord
include ObjectStorage::BackgroundMove
has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- has_many :projects, through: :lfs_objects_projects
+ has_many :projects, -> { distinct }, through: :lfs_objects_projects
scope :with_files_stored_locally, -> { where(file_store: LfsObjectUploader::Store::LOCAL) }
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index f9afb18c1d7..e45c56b6394 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -5,11 +5,17 @@ class LfsObjectsProject < ApplicationRecord
belongs_to :lfs_object
validates :lfs_object_id, presence: true
- validates :lfs_object_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
+ validates :lfs_object_id, uniqueness: { scope: [:project_id, :repository_type], message: "already exists in repository" }
validates :project_id, presence: true
after_commit :update_project_statistics, on: [:create, :destroy]
+ enum repository_type: {
+ project: 0,
+ wiki: 1,
+ design: 2 ## EE-specific
+ }
+
private
def update_project_statistics
diff --git a/app/models/note.rb b/app/models/note.rb
index 081d6f91230..15271c68a9e 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -342,7 +342,7 @@ class Note < ApplicationRecord
end
def to_ability_name
- for_snippet? ? noteable.class.name.underscore : noteable_type.underscore
+ for_snippet? ? noteable.class.name.underscore : noteable_type.demodulize.underscore
end
def can_be_discussion_note?
diff --git a/app/models/project.rb b/app/models/project.rb
index 9d17d68eee2..351d08eaf63 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -72,7 +72,6 @@ class Project < ApplicationRecord
delegate :no_import?, to: :import_state, allow_nil: true
default_value_for :archived, false
- default_value_for(:visibility_level) { Gitlab::CurrentSettings.default_project_visibility }
default_value_for :resolve_outdated_diff_discussions, false
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { Gitlab::CurrentSettings.pick_repository_storage }
@@ -223,7 +222,7 @@ class Project < ApplicationRecord
has_many :starrers, through: :users_star_projects, source: :user
has_many :releases
has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- has_many :lfs_objects, through: :lfs_objects_projects
+ has_many :lfs_objects, -> { distinct }, through: :lfs_objects_projects
has_many :lfs_file_locks
has_many :project_group_links
has_many :invited_groups, through: :project_group_links, source: :group
@@ -613,6 +612,23 @@ class Project < ApplicationRecord
end
end
+ def initialize(attributes = {})
+ # We can't use default_value_for because the database has a default
+ # value of 0 for visibility_level. If someone attempts to create a
+ # private project, default_value_for will assume that the
+ # visibility_level hasn't changed and will use the application
+ # setting default, which could be internal or public. For projects
+ # inside a private group, those levels are invalid.
+ #
+ # To fix the problem, we assign the actual default in the application if
+ # no explicit visibility has been initialized.
+ unless visibility_attribute_present?(attributes)
+ attributes[:visibility_level] = Gitlab::CurrentSettings.default_project_visibility
+ end
+
+ super
+ end
+
def all_pipelines
if builds_enabled?
super
diff --git a/app/models/project_services/data_fields.rb b/app/models/project_services/data_fields.rb
new file mode 100644
index 00000000000..438d85098c8
--- /dev/null
+++ b/app/models/project_services/data_fields.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module DataFields
+ extend ActiveSupport::Concern
+
+ included do
+ has_one :issue_tracker_data
+ has_one :jira_tracker_data
+ end
+end
diff --git a/app/models/project_services/deployment_service.rb b/app/models/project_services/deployment_service.rb
deleted file mode 100644
index 80aa2101509..00000000000
--- a/app/models/project_services/deployment_service.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-# Base class for deployment services
-#
-# These services integrate with a deployment solution like Kubernetes/OpenShift,
-# Mesosphere, etc, to provide additional features to environments.
-class DeploymentService < Service
- default_value_for :category, 'deployment'
-
- def self.supported_events
- %w()
- end
-
- def predefined_variables(project:)
- []
- end
-
- # Environments may have a number of terminals. Should return an array of
- # hashes describing them, e.g.:
- #
- # [{
- # :selectors => {"a" => "b", "foo" => "bar"},
- # :url => "wss://external.example.com/exec",
- # :headers => {"Authorization" => "Token xxx"},
- # :subprotocols => ["foo"],
- # :ca_pem => "----BEGIN CERTIFICATE...", # optional
- # :created_at => Time.now.utc
- # }]
- #
- # Selectors should be a set of values that uniquely identify a particular
- # terminal
- def terminals(environment)
- raise NotImplementedError
- end
-
- def can_test?
- false
- end
-end
diff --git a/app/models/project_services/issue_tracker_data.rb b/app/models/project_services/issue_tracker_data.rb
new file mode 100644
index 00000000000..2c1d28ed421
--- /dev/null
+++ b/app/models/project_services/issue_tracker_data.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class IssueTrackerData < ApplicationRecord
+ belongs_to :service
+
+ delegate :activated?, to: :service, allow_nil: true
+
+ validates :service, presence: true
+ validates :project_url, presence: true, public_url: { enforce_sanitization: true }, if: :activated?
+ validates :issues_url, presence: true, public_url: { enforce_sanitization: true }, if: :activated?
+ validates :new_issue_url, public_url: { enforce_sanitization: true }, if: :activated?
+
+ def self.encryption_options
+ {
+ key: Settings.attr_encrypted_db_key_base_32,
+ encode: true,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm'
+ }
+ end
+
+ attr_encrypted :project_url, encryption_options
+ attr_encrypted :issues_url, encryption_options
+ attr_encrypted :new_issue_url, encryption_options
+end
diff --git a/app/models/project_services/jira_tracker_data.rb b/app/models/project_services/jira_tracker_data.rb
new file mode 100644
index 00000000000..4f528e3d81b
--- /dev/null
+++ b/app/models/project_services/jira_tracker_data.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class JiraTrackerData < ApplicationRecord
+ belongs_to :service
+
+ delegate :activated?, to: :service, allow_nil: true
+
+ validates :service, presence: true
+ validates :url, public_url: { enforce_sanitization: true }, presence: true, if: :activated?
+ validates :api_url, public_url: { enforce_sanitization: true }, allow_blank: true
+ validates :username, presence: true, if: :activated?
+ validates :password, presence: true, if: :activated?
+ validates :jira_issue_transition_id,
+ format: { with: Gitlab::Regex.jira_transition_id_regex, message: s_("JiraService|transition ids can have only numbers which can be split with , or ;") },
+ allow_blank: true
+
+ def self.encryption_options
+ {
+ key: Settings.attr_encrypted_db_key_base_32,
+ encode: true,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm'
+ }
+ end
+
+ attr_encrypted :url, encryption_options
+ attr_encrypted :api_url, encryption_options
+ attr_encrypted :username, encryption_options
+ attr_encrypted :password, encryption_options
+end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index aa6b4aa1d5e..edf7e886e77 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -5,10 +5,12 @@
# We'll move this class to Clusters::Platforms::Kubernetes, which contains exactly the same logic.
# After we've migrated data, we'll remove KubernetesService. This would happen in a few months.
# If you're modyfiyng this class, please note that you should update the same change in Clusters::Platforms::Kubernetes.
-class KubernetesService < DeploymentService
+class KubernetesService < Service
include Gitlab::Kubernetes
include ReactiveCaching
+ default_value_for :category, 'deployment'
+
self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
# Namespace defaults to the project path, but can be overridden in case that
@@ -32,7 +34,10 @@ class KubernetesService < DeploymentService
before_validation :enforce_namespace_to_lower_case
- validate :deprecation_validation, unless: :template?
+ attr_accessor :skip_deprecation_validation
+
+ validate :deprecation_validation, unless: :skip_deprecation_validation
+
validates :namespace,
allow_blank: true,
length: 1..63,
@@ -44,6 +49,14 @@ class KubernetesService < DeploymentService
after_save :clear_reactive_cache!
+ def self.supported_events
+ %w()
+ end
+
+ def can_test?
+ false
+ end
+
def initialize_properties
self.properties = {} if properties.nil?
end
@@ -56,11 +69,6 @@ class KubernetesService < DeploymentService
'Kubernetes / OpenShift integration'
end
- def help
- 'To enable terminal access to Kubernetes environments, label your ' \
- 'deployments with `app=$CI_ENVIRONMENT_SLUG`'
- end
-
def self.to_param
'kubernetes'
end
@@ -153,14 +161,25 @@ class KubernetesService < DeploymentService
end
def deprecated?
- !active
+ true
+ end
+
+ def editable?
+ false
end
def deprecation_message
- content = _("Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page") % {
- deprecated_message_content: deprecated_message_content,
- url: Gitlab::Routing.url_helpers.project_clusters_path(project)
- }
+ content = if project
+ _("Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page") % {
+ deprecated_message_content: deprecated_message_content,
+ url: Gitlab::Routing.url_helpers.project_clusters_path(project)
+ }
+ else
+ _("The instance-level Kubernetes service integration is deprecated. Your data has been migrated to an <a href=\"%{url}\"/>instance-level cluster</a>.") % {
+ url: Gitlab::Routing.url_helpers.admin_clusters_path
+ }
+ end
+
content.html_safe
end
@@ -243,10 +262,6 @@ class KubernetesService < DeploymentService
end
def deprecated_message_content
- if active?
- _("Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure")
- else
- _("Fields on this page are now uneditable, you can configure")
- end
+ _("Fields on this page are now uneditable, you can configure")
end
end
diff --git a/app/models/project_services/mock_deployment_service.rb b/app/models/project_services/mock_deployment_service.rb
index 7ab1687f8ba..1103cb11e73 100644
--- a/app/models/project_services/mock_deployment_service.rb
+++ b/app/models/project_services/mock_deployment_service.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
-class MockDeploymentService < DeploymentService
+class MockDeploymentService < Service
+ default_value_for :category, 'deployment'
+
def title
'Mock deployment'
end
@@ -17,4 +19,16 @@ class MockDeploymentService < DeploymentService
def terminals(environment)
[]
end
+
+ def self.supported_events
+ %w()
+ end
+
+ def predefined_variables(project:)
+ []
+ end
+
+ def can_test?
+ false
+ end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 9896aa12e90..40033003f3b 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -6,6 +6,7 @@ class Service < ApplicationRecord
include Sortable
include Importable
include ProjectServicesLoggable
+ include DataFields
serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize
@@ -119,7 +120,7 @@ class Service < ApplicationRecord
end
def self.event_names
- self.supported_events.map { |event| "#{event}_events" }
+ self.supported_events.map { |event| ServicesHelper.service_event_field_name(event) }
end
def event_field(event)
@@ -151,7 +152,7 @@ class Service < ApplicationRecord
end
def self.supported_events
- %w(push tag_push issue confidential_issue merge_request wiki_page)
+ %w(commit push tag_push issue confidential_issue merge_request wiki_page)
end
def execute(data)
diff --git a/app/models/user_callout_enums.rb b/app/models/user_callout_enums.rb
index 54b0d2ce831..7b68e5076c7 100644
--- a/app/models/user_callout_enums.rb
+++ b/app/models/user_callout_enums.rb
@@ -6,12 +6,15 @@ module UserCalloutEnums
#
# This method is separate from the `UserCallout` model so that it can be
# extended by EE.
+ #
+ # If you are going to add new items to this hash, check that you're not going
+ # to conflict with EE-only values: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/models/ee/user_callout_enums.rb
def self.feature_names
{
gke_cluster_integration: 1,
gcp_signup_offer: 2,
cluster_security_warning: 3,
- suggest_popover_dismissed: 4
+ suggest_popover_dismissed: 9
}
end
end
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 5dd2279ef99..82bf9bf8bf6 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -7,6 +7,10 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0
condition(:admin) { @user&.admin? }
+ desc "User is blocked"
+ with_options scope: :user, score: 0
+ condition(:blocked) { @user&.blocked? }
+
desc "User has access to all private groups & projects"
with_options scope: :user, score: 0
condition(:full_private_access) { @user&.full_private_access? }
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index e85397422e6..134de1c9ace 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -1,10 +1,6 @@
# frozen_string_literal: true
class GlobalPolicy < BasePolicy
- desc "User is blocked"
- with_options scope: :user, score: 0
- condition(:blocked) { @user&.blocked? }
-
desc "User is an internal user"
with_options scope: :user, score: 0
condition(:internal) { @user&.internal? }
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 728a3040227..b3e29e775fc 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -258,6 +258,7 @@ class ProjectPolicy < BasePolicy
enable :resolve_note
enable :create_container_image
enable :update_container_image
+ enable :destroy_container_image
enable :create_environment
enable :create_deployment
enable :create_release
@@ -446,6 +447,10 @@ class ProjectPolicy < BasePolicy
prevent :owner_access
end
+ rule { blocked }.policy do
+ prevent :create_pipeline
+ end
+
private
def team_member?
diff --git a/app/policies/project_statistics_policy.rb b/app/policies/project_statistics_policy.rb
new file mode 100644
index 00000000000..c0592f1ea13
--- /dev/null
+++ b/app/policies/project_statistics_policy.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ProjectStatisticsPolicy < BasePolicy
+ delegate { @subject.project }
+end
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index ec2698ecbe3..9ef93b2387f 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -4,7 +4,6 @@ class PipelineEntity < Grape::Entity
include RequestAwareEntity
expose :id
- expose :iid
expose :user, using: UserEntity
expose :active?, as: :active
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index fd5442a6c28..f2cd51ef4d0 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -3,7 +3,7 @@
module Files
class CreateService < Files::BaseService
def create_commit!
- transformer = Lfs::FileTransformer.new(project, @branch_name)
+ transformer = Lfs::FileTransformer.new(project, repository, @branch_name)
result = transformer.new_file(@file_path, @file_content)
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index c1bc26c330a..d8c4e5bc5e8 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -5,7 +5,7 @@ module Files
UPDATE_FILE_ACTIONS = %w(update move delete chmod).freeze
def create_commit!
- transformer = Lfs::FileTransformer.new(project, @branch_name)
+ transformer = Lfs::FileTransformer.new(project, repository, @branch_name)
actions = actions_after_lfs_transformation(transformer, params[:actions])
actions = transform_move_actions(actions)
diff --git a/app/services/lfs/file_transformer.rb b/app/services/lfs/file_transformer.rb
index 5239fe1b6e3..d1746399908 100644
--- a/app/services/lfs/file_transformer.rb
+++ b/app/services/lfs/file_transformer.rb
@@ -8,17 +8,17 @@ module Lfs
# pointer returned. If the file isn't in LFS the untransformed content
# is returned to save in the commit.
#
- # transformer = Lfs::FileTransformer.new(project, @branch_name)
+ # transformer = Lfs::FileTransformer.new(project, repository, @branch_name)
# content_or_lfs_pointer = transformer.new_file(file_path, content).content
# create_transformed_commit(content_or_lfs_pointer)
#
class FileTransformer
- attr_reader :project, :branch_name
+ attr_reader :project, :repository, :repository_type, :branch_name
- delegate :repository, to: :project
-
- def initialize(project, branch_name)
+ def initialize(project, repository, branch_name)
@project = project
+ @repository = repository
+ @repository_type = repository.repo_type.name
@branch_name = branch_name
end
@@ -64,7 +64,11 @@ module Lfs
# rubocop: enable CodeReuse/ActiveRecord
def link_lfs_object!(lfs_object)
- project.lfs_objects << lfs_object
+ LfsObjectsProject.safe_find_or_create_by!(
+ project: project,
+ lfs_object: lfs_object,
+ repository_type: repository_type
+ )
end
def parse_file_content(file_content, encoding: nil)
diff --git a/app/views/admin/application_settings/ci_cd.html.haml b/app/views/admin/application_settings/ci_cd.html.haml
index db24c9982f7..a2aa1687f80 100644
--- a/app/views/admin/application_settings/ci_cd.html.haml
+++ b/app/views/admin/application_settings/ci_cd.html.haml
@@ -13,6 +13,8 @@
.settings-content
= render 'ci_cd'
+= render_if_exists 'admin/application_settings/required_instance_ci_setting', expanded: expanded_by_default?
+
- if Gitlab.config.registry.enabled
%section.settings.as-registry.no-animate#js-registry-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
diff --git a/app/views/admin/services/_deprecated_message.html.haml b/app/views/admin/services/_deprecated_message.html.haml
new file mode 100644
index 00000000000..fea9506a4bb
--- /dev/null
+++ b/app/views/admin/services/_deprecated_message.html.haml
@@ -0,0 +1,3 @@
+.flash-container.flash-container-page
+ .flash-alert.deprecated-service
+ %span= @service.deprecation_message
diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml
index 1798b44bbb7..97373a3c350 100644
--- a/app/views/admin/services/_form.html.haml
+++ b/app/views/admin/services/_form.html.haml
@@ -6,5 +6,6 @@
= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'fieldset-form' } do |form|
= render 'shared/service_settings', form: form, subject: @service
- .footer-block.row-content-block
- = form.submit 'Save', class: 'btn btn-success'
+ - unless @service.is_a?(KubernetesService)
+ .footer-block.row-content-block
+ = form.submit 'Save', class: 'btn btn-success'
diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml
index 512176649e6..79f5ab0d77d 100644
--- a/app/views/admin/services/edit.html.haml
+++ b/app/views/admin/services/edit.html.haml
@@ -1,4 +1,7 @@
- add_to_breadcrumbs "Service Templates", admin_application_settings_services_path
- breadcrumb_title @service.title
- page_title @service.title, "Service Templates"
+
+= render 'deprecated_message' if @service.deprecation_message
+
= render 'form'
diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml
index 70e2eaeaf3b..4d3e3359ea0 100644
--- a/app/views/clusters/clusters/gcp/_form.html.haml
+++ b/app/views/clusters/clusters/gcp/_form.html.haml
@@ -65,15 +65,6 @@
%p.form-text.text-muted
= s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end }
- .form-group
- = provider_gcp_field.check_box :legacy_abac, { label: s_('ClusterIntegration|RBAC-enabled cluster'),
- label_class: 'label-bold' }, false, true
- .form-text.text-muted
- = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
- = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
- = link_to _('More information'), help_page_path('user/project/clusters/index.md',
- anchor: 'role-based-access-control-rbac-core-only'), target: '_blank'
-
.form-group
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
label_class: 'label-bold' }
diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml
index c1727cf9079..f2e44462226 100644
--- a/app/views/clusters/platforms/kubernetes/_form.html.haml
+++ b/app/views/clusters/platforms/kubernetes/_form.html.haml
@@ -48,7 +48,7 @@
= s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
.form-group
- = field.check_box :managed, { disabled: true, label: s_('ClusterIntegration|GitLab-managed cluster'),
+ = field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.')
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index 0a14830c666..0da1f1ba7f5 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -17,6 +17,7 @@
%br
%span.descr.text-muted= share_with_group_lock_help_text(@group)
+ = render_if_exists 'groups/settings/ip_restriction', f: f, group: @group
= render 'groups/settings/lfs', f: f
= render 'groups/settings/project_creation_level', f: f, group: @group
= render 'groups/settings/two_factor_auth', f: f
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index c357207054b..7535aee83a3 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -78,3 +78,4 @@
= 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_if_exists 'layouts/snowplow'
+ = render_if_exists 'layouts/pendo' if Feature.enabled?(:pendo_tracking) && !Rails.env.test?
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index bdf7b933ab8..f4560404c03 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -53,10 +53,9 @@
%span.badge.badge-info= _('manual')
- if pipeline_link
- %td.pipeline-link
- = link_to pipeline_path(pipeline), class: 'has-tooltip', title: _('Pipeline ID (IID)') do
+ %td
+ = link_to pipeline_path(pipeline) do
%span.pipeline-id ##{pipeline.id}
- %span.pipeline-iid (##{pipeline.iid})
%span by
- if pipeline.user
= user_avatar(user: pipeline.user, size: 20)
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 77ea2c04b28..a766dd51463 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -81,7 +81,7 @@
= link_to project_pipeline_path(@project, last_pipeline.id), class: "ci-status-icon-#{last_pipeline.status}" do
= ci_icon_for_status(last_pipeline.status)
#{ _('Pipeline') }
- = link_to "##{last_pipeline.id} (##{last_pipeline.iid})", project_pipeline_path(@project, last_pipeline.id), class: "has-tooltip", title: _('Pipeline ID (IID)')
+ = link_to "##{last_pipeline.id}", project_pipeline_path(@project, last_pipeline.id)
= ci_label_for_status(last_pipeline.status)
- if last_pipeline.stages_count.nonzero?
#{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) }
diff --git a/app/views/projects/merge_requests/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml
index 7f2c9dcacfd..4f09f47d795 100644
--- a/app/views/projects/merge_requests/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/_mr_box.html.haml
@@ -1,10 +1,10 @@
.detail-page-description
- %h2.title
+ %h2.title.qa-title
= markdown_field(@merge_request, :title)
%div
- if @merge_request.description.present?
- .description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' }
+ .description.qa-description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' }
.md
= markdown_field(@merge_request, :description)
%textarea.hidden.js-task-list-field
diff --git a/app/views/shared/projects/_search_form.html.haml b/app/views/shared/projects/_search_form.html.haml
index 7c7c0a363ac..4365e3f6877 100644
--- a/app/views/shared/projects/_search_form.html.haml
+++ b/app/views/shared/projects/_search_form.html.haml
@@ -1,7 +1,7 @@
- form_field_classes = local_assigns[:admin_view] || !Feature.enabled?(:project_list_filter_bar) ? 'input-short js-projects-list-filter' : ''
- placeholder = local_assigns[:search_form_placeholder] ? search_form_placeholder : 'Filter by name...'
-= form_tag filter_projects_path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
+= form_tag filter_projects_path, method: :get, class: 'project-filter-form qa-project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :name, params[:name],
placeholder: placeholder,
class: "project-filter-form-field form-control #{form_field_classes}",