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:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-01-13 15:10:27 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-13 15:10:27 +0300
commit39c1496527de559d5d3a5c3b53d11575f435a4dc (patch)
tree51ed818b49752bb27d8bc7a13e9efcb3e5192c1f /app
parentab9c1dbb2dc0e591a6ce4466e15766d99f4abf4b (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/artifacts_settings/graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql5
-rw-r--r--app/assets/javascripts/artifacts_settings/graphql/queries/get_keep_latest_artifact_project_setting.query.graphql7
-rw-r--r--app/assets/javascripts/artifacts_settings/index.js32
-rw-r--r--app/assets/javascripts/artifacts_settings/keep_latest_artifact_checkbox.vue99
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue21
-rw-r--r--app/assets/javascripts/boards/graphql/board_create.mutation.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/board_update.mutation.graphql4
-rw-r--r--app/assets/javascripts/ide/lib/languages/hcl.js2
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_trigger.vue7
-rw-r--r--app/assets/javascripts/merge_request/components/status_box.vue31
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js4
-rw-r--r--app/assets/javascripts/pages/projects/project_members/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js2
-rw-r--r--app/controllers/jira_connect/app_descriptor_controller.rb14
-rw-r--r--app/controllers/metrics_controller.rb10
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/graphql/resolvers/ci/config_resolver.rb55
-rw-r--r--app/graphql/types/ci/config/job_restriction_type.rb15
-rw-r--r--app/graphql/types/ci/config/job_type.rb31
-rw-r--r--app/models/commit.rb6
-rw-r--r--app/models/merge_request.rb20
-rw-r--r--app/serializers/merge_request_poll_cached_widget_entity.rb8
-rw-r--r--app/services/feature_flags/base_service.rb15
-rw-r--r--app/views/groups/group_members/index.html.haml34
-rw-r--r--app/views/profiles/two_factor_auths/_codes.html.haml17
-rw-r--r--app/views/profiles/two_factor_auths/create.html.haml4
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml9
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml9
-rw-r--r--app/views/projects/project_members/index.html.haml36
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml11
-rw-r--r--app/workers/all_queues.yml8
-rw-r--r--app/workers/jira_connect/sync_feature_flags_worker.rb24
34 files changed, 395 insertions, 159 deletions
diff --git a/app/assets/javascripts/artifacts_settings/graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql b/app/assets/javascripts/artifacts_settings/graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql
new file mode 100644
index 00000000000..d50fd665c16
--- /dev/null
+++ b/app/assets/javascripts/artifacts_settings/graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql
@@ -0,0 +1,5 @@
+mutation updateKeepLatestArtifactProjectSetting($fullPath: ID!, $keepLatestArtifact: Boolean!) {
+ ciCdSettingsUpdate(input: { fullPath: $fullPath, keepLatestArtifact: $keepLatestArtifact }) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/artifacts_settings/graphql/queries/get_keep_latest_artifact_project_setting.query.graphql b/app/assets/javascripts/artifacts_settings/graphql/queries/get_keep_latest_artifact_project_setting.query.graphql
new file mode 100644
index 00000000000..7486512c57c
--- /dev/null
+++ b/app/assets/javascripts/artifacts_settings/graphql/queries/get_keep_latest_artifact_project_setting.query.graphql
@@ -0,0 +1,7 @@
+query getKeepLatestArtifactProjectSetting($fullPath: ID!) {
+ project(fullPath: $fullPath) {
+ ciCdSettings {
+ keepLatestArtifact
+ }
+ }
+}
diff --git a/app/assets/javascripts/artifacts_settings/index.js b/app/assets/javascripts/artifacts_settings/index.js
new file mode 100644
index 00000000000..d99d2be81cf
--- /dev/null
+++ b/app/assets/javascripts/artifacts_settings/index.js
@@ -0,0 +1,32 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import KeepLatestArtifactCheckbox from '~/artifacts_settings/keep_latest_artifact_checkbox.vue';
+
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+export default (containerId = 'js-artifacts-settings-app') => {
+ const containerEl = document.getElementById(containerId);
+
+ if (!containerEl) {
+ return false;
+ }
+
+ const { fullPath, helpPagePath } = containerEl.dataset;
+
+ return new Vue({
+ el: containerEl,
+ apolloProvider,
+ provide: {
+ fullPath,
+ helpPagePath,
+ },
+ render(createElement) {
+ return createElement(KeepLatestArtifactCheckbox);
+ },
+ });
+};
diff --git a/app/assets/javascripts/artifacts_settings/keep_latest_artifact_checkbox.vue b/app/assets/javascripts/artifacts_settings/keep_latest_artifact_checkbox.vue
new file mode 100644
index 00000000000..5684033f3af
--- /dev/null
+++ b/app/assets/javascripts/artifacts_settings/keep_latest_artifact_checkbox.vue
@@ -0,0 +1,99 @@
+<script>
+import { GlAlert, GlFormCheckbox, GlLink } from '@gitlab/ui';
+import { __ } from '~/locale';
+import GetKeepLatestArtifactProjectSetting from './graphql/queries/get_keep_latest_artifact_project_setting.query.graphql';
+import UpdateKeepLatestArtifactProjectSetting from './graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql';
+
+const FETCH_ERROR = __('There was a problem fetching the keep latest artifact setting.');
+const UPDATE_ERROR = __('There was a problem updating the keep latest artifact setting.');
+
+export default {
+ components: {
+ GlAlert,
+ GlFormCheckbox,
+ GlLink,
+ },
+ inject: {
+ fullPath: {
+ default: '',
+ },
+ helpPagePath: {
+ default: '',
+ },
+ },
+ apollo: {
+ keepLatestArtifact: {
+ query: GetKeepLatestArtifactProjectSetting,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ };
+ },
+ update(data) {
+ return data.project?.ciCdSettings?.keepLatestArtifact;
+ },
+ error() {
+ this.reportError(FETCH_ERROR);
+ },
+ },
+ },
+ data() {
+ return {
+ keepLatestArtifact: true,
+ errorMessage: '',
+ isAlertDismissed: false,
+ };
+ },
+ computed: {
+ shouldShowAlert() {
+ return this.errorMessage && !this.isAlertDismissed;
+ },
+ },
+ methods: {
+ reportError(error) {
+ this.errorMessage = error;
+ this.isAlertDismissed = false;
+ },
+ async updateSetting(checked) {
+ try {
+ const { data } = await this.$apollo.mutate({
+ mutation: UpdateKeepLatestArtifactProjectSetting,
+ variables: {
+ fullPath: this.fullPath,
+ keepLatestArtifact: checked,
+ },
+ });
+
+ if (data.ciCdSettingsUpdate.errors.length) {
+ this.reportError(UPDATE_ERROR);
+ }
+ } catch (error) {
+ this.reportError(UPDATE_ERROR);
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-alert
+ v-if="shouldShowAlert"
+ class="gl-mb-5"
+ variant="danger"
+ @dismiss="isAlertDismissed = true"
+ >{{ errorMessage }}</gl-alert
+ >
+ <gl-form-checkbox v-model="keepLatestArtifact" @change="updateSetting"
+ ><b class="gl-mr-3">{{ __('Keep artifacts from most recent successful jobs') }}</b>
+ <gl-link :href="helpPagePath">{{ __('More information') }}</gl-link></gl-form-checkbox
+ >
+ <p>
+ {{
+ __(
+ 'The latest artifacts created by jobs in the most recent successful pipeline will be stored.',
+ )
+ }}
+ </p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 4bce0de7c12..c701ecd3040 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -2,8 +2,9 @@
import { GlModal } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { deprecatedCreateFlash as Flash } from '~/flash';
-import { visitUrl, stripFinalUrlSegment } from '~/lib/utils/url_utility';
-import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { getParameterByName } from '~/lib/utils/common_utils';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
import boardsStore from '~/boards/stores/boards_store';
import { fullLabelId, fullBoardId } from '../boards_util';
@@ -216,9 +217,15 @@ export default {
variables: { input: this.mutationVariables },
});
- return this.board.id
- ? getIdFromGraphQLId(response.data.updateBoard.board.id)
- : getIdFromGraphQLId(response.data.createBoard.board.id);
+ if (!this.board.id) {
+ return response.data.createBoard.board.webPath;
+ }
+
+ const path = response.data.updateBoard.board.webPath;
+ const param = getParameterByName('group_by')
+ ? `?group_by=${getParameterByName('group_by')}`
+ : '';
+ return `${path}${param}`;
},
async submit() {
if (this.board.name.length === 0) return;
@@ -239,9 +246,7 @@ export default {
}
} else {
try {
- const path = await this.createOrUpdateBoard();
- const strippedUrl = stripFinalUrlSegment(window.location.href);
- const url = strippedUrl.includes('boards') ? `${path}` : `boards/${path}`;
+ const url = await this.createOrUpdateBoard();
visitUrl(url);
} catch {
Flash(this.$options.i18n.saveErrorMessage);
diff --git a/app/assets/javascripts/boards/graphql/board_create.mutation.graphql b/app/assets/javascripts/boards/graphql/board_create.mutation.graphql
index e26d67dcc0e..b3ea79d6443 100644
--- a/app/assets/javascripts/boards/graphql/board_create.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/board_create.mutation.graphql
@@ -2,6 +2,8 @@ mutation createBoard($input: CreateBoardInput!) {
createBoard(input: $input) {
board {
id
+ webPath
}
+ errors
}
}
diff --git a/app/assets/javascripts/boards/graphql/board_update.mutation.graphql b/app/assets/javascripts/boards/graphql/board_update.mutation.graphql
index 6b4ea2bef1a..3abe09079c7 100644
--- a/app/assets/javascripts/boards/graphql/board_update.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/board_update.mutation.graphql
@@ -2,8 +2,8 @@ mutation UpdateBoard($input: UpdateBoardInput!) {
updateBoard(input: $input) {
board {
id
- hideClosedList
- hideBacklogList
+ webPath
}
+ errors
}
}
diff --git a/app/assets/javascripts/ide/lib/languages/hcl.js b/app/assets/javascripts/ide/lib/languages/hcl.js
index d7a6491297b..bbb2ca66f33 100644
--- a/app/assets/javascripts/ide/lib/languages/hcl.js
+++ b/app/assets/javascripts/ide/lib/languages/hcl.js
@@ -144,7 +144,7 @@ const language = {
],
heredocBody: [
[
- /^([\w\-]+)$/,
+ /([\w\-]+)$/,
{
cases: {
'$1==$S2': [
diff --git a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
index d133e3655e3..eb97c458f88 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
@@ -19,6 +19,11 @@ export default {
required: false,
default: '',
},
+ classes: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
methods: {
openModal() {
@@ -29,7 +34,7 @@ export default {
</script>
<template>
- <gl-link @click="openModal">
+ <gl-link :class="classes" @click="openModal">
<div v-if="icon" class="nav-icon-container">
<gl-icon :size="16" :name="icon" />
</div>
diff --git a/app/assets/javascripts/merge_request/components/status_box.vue b/app/assets/javascripts/merge_request/components/status_box.vue
index c29f7b86df9..fd99802caff 100644
--- a/app/assets/javascripts/merge_request/components/status_box.vue
+++ b/app/assets/javascripts/merge_request/components/status_box.vue
@@ -1,5 +1,5 @@
<script>
-import { GlIcon, GlSprintf, GlLink } from '@gitlab/ui';
+import { GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import mrEventHub from '../eventhub';
@@ -18,29 +18,16 @@ const STATUS = {
export default {
components: {
GlIcon,
- GlSprintf,
- GlLink,
},
props: {
initialState: {
type: String,
required: true,
},
- initialIsReverted: {
- type: Boolean,
- required: true,
- },
- initialRevertedPath: {
- type: String,
- required: false,
- default: null,
- },
},
data() {
return {
state: this.initialState,
- isReverted: this.initialIsReverted,
- revertedPath: this.initialRevertedPath,
};
},
computed: {
@@ -61,10 +48,8 @@ export default {
mrEventHub.$off('mr.state.updated', this.updateState);
},
methods: {
- updateState({ state, reverted, revertedPath }) {
+ updateState({ state }) {
this.state = state;
- this.reverted = reverted;
- this.revertedPath = revertedPath;
},
},
};
@@ -78,17 +63,7 @@ export default {
data-testid="status-icon"
/>
<span class="gl-display-none gl-display-sm-block">
- <gl-sprintf v-if="isReverted" :message="__('Merged (%{linkStart}reverted%{linkEnd})')">
- <template #link="{ content }">
- <gl-link
- :href="revertedPath"
- class="gl-reset-color! gl-text-decoration-underline"
- data-testid="reverted-link"
- >{{ content }}</gl-link
- >
- </template>
- </gl-sprintf>
- <template v-else>{{ statusHumanName }}</template>
+ {{ statusHumanName }}
</span>
</div>
</template>
diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js
index dbc65f67a98..5346e3720e8 100644
--- a/app/assets/javascripts/pages/groups/group_members/index.js
+++ b/app/assets/javascripts/pages/groups/group_members/index.js
@@ -4,6 +4,8 @@ import UsersSelect from '~/users_select';
import groupsSelect from '~/groups_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
import { initGroupMembersApp } from '~/groups/members';
+import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
+import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { memberRequestFormatter, groupLinkRequestFormatter } from '~/groups/members/utils';
import { s__ } from '~/locale';
@@ -64,5 +66,7 @@ groupsSelect();
memberExpirationDate();
memberExpirationDate('.js-access-expiration-date-groups');
mountRemoveMemberModal();
+initInviteMembersModal();
+initInviteMembersTrigger();
new UsersSelect(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
index 7657cea5bcd..1a0c5860991 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import ZenMode from '~/zen_mode';
import initIssuableSidebar from '~/init_issuable_sidebar';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
-import { handleLocationHash, parseBoolean } from '~/lib/utils/common_utils';
+import { handleLocationHash } from '~/lib/utils/common_utils';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
import initSourcegraph from '~/sourcegraph';
import loadAwardsHandler from '~/awards_handler';
@@ -29,8 +29,6 @@ export default function () {
return h(StatusBox, {
props: {
initialState: el.dataset.state,
- initialIsReverted: parseBoolean(el.dataset.isReverted),
- initialRevertedPath: el.dataset.revertedPath,
},
});
},
diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js
index 664f0a2dc93..3e0a48ee6a2 100644
--- a/app/assets/javascripts/pages/projects/project_members/index.js
+++ b/app/assets/javascripts/pages/projects/project_members/index.js
@@ -4,6 +4,8 @@ import memberExpirationDate from '~/member_expiration_date';
import UsersSelect from '~/users_select';
import groupsSelect from '~/groups_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
+import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
+import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
function mountRemoveMemberModal() {
const el = document.querySelector('.js-remove-member-modal');
@@ -24,6 +26,8 @@ document.addEventListener('DOMContentLoaded', () => {
memberExpirationDate();
memberExpirationDate('.js-access-expiration-date-groups');
mountRemoveMemberModal();
+ initInviteMembersModal();
+ initInviteMembersTrigger();
new Members(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index 35c0696f0f1..1321155b7ec 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -5,6 +5,7 @@ import initVariableList from '~/ci_variable_list';
import initDeployFreeze from '~/deploy_freeze';
import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_toggle';
+import initArtifactsSettings from '~/artifacts_settings';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
@@ -33,6 +34,7 @@ document.addEventListener('DOMContentLoaded', () => {
initDeployFreeze();
initSettingsPipelinesTriggers();
+ initArtifactsSettings();
if (gon?.features?.vueifySharedRunnersToggle) {
initSharedRunnersToggle();
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index da4dc777c3e..a6bbab47a06 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -158,8 +158,6 @@ export default class MergeRequestStore {
mrEventHub.$emit('mr.state.updated', {
state: this.mergeRequestState,
- reverted: data.reverted,
- reverted_path: data.revertedPath,
});
}
diff --git a/app/controllers/jira_connect/app_descriptor_controller.rb b/app/controllers/jira_connect/app_descriptor_controller.rb
index 4cee6e5b479..137f830e40b 100644
--- a/app/controllers/jira_connect/app_descriptor_controller.rb
+++ b/app/controllers/jira_connect/app_descriptor_controller.rb
@@ -66,6 +66,7 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController
modules.merge!(build_information_module)
modules.merge!(deployment_information_module)
+ modules.merge!(feature_flag_module)
modules
end
@@ -85,6 +86,19 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController
}
end
+ # see: https://developer.atlassian.com/cloud/jira/software/modules/feature-flag/
+ def feature_flag_module
+ {
+ jiraFeatureFlagInfoProvider: common_module_properties.merge(
+ actions: {}, # TODO: create, link and list feature flags https://gitlab.com/gitlab-org/gitlab/-/issues/297386
+ name: {
+ value: 'GitLab Feature Flags'
+ },
+ key: 'gitlab-feature-flags'
+ )
+ }
+ end
+
# See: https://developer.atlassian.com/cloud/jira/software/modules/build/
def build_information_module
{
diff --git a/app/controllers/metrics_controller.rb b/app/controllers/metrics_controller.rb
index c2089a0fca3..1ef1e12bb02 100644
--- a/app/controllers/metrics_controller.rb
+++ b/app/controllers/metrics_controller.rb
@@ -18,9 +18,19 @@ class MetricsController < ActionController::Base
render plain: response, content_type: 'text/plain; version=0.0.4'
end
+ def system
+ render json: system_metrics
+ end
+
private
def metrics_service
@metrics_service ||= MetricsService.new
end
+
+ def system_metrics
+ Gitlab::Metrics::System.summary.merge(
+ worker_id: Prometheus::PidProvider.worker_id
+ )
+ end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2a4e382e27a..59f2a1539ef 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -35,7 +35,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:approvals_commented_by, @project, default_enabled: true)
push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true)
push_frontend_feature_flag(:merge_request_widget_graphql, @project)
- push_frontend_feature_flag(:unified_diff_components, @project)
+ push_frontend_feature_flag(:unified_diff_components, @project, default_enabled: true)
push_frontend_feature_flag(:default_merge_ref_for_diffs, @project)
push_frontend_feature_flag(:core_security_mr_widget, @project, default_enabled: true)
push_frontend_feature_flag(:core_security_mr_widget_counts, @project)
diff --git a/app/graphql/resolvers/ci/config_resolver.rb b/app/graphql/resolvers/ci/config_resolver.rb
index bea91b5540f..72d3ae30d73 100644
--- a/app/graphql/resolvers/ci/config_resolver.rb
+++ b/app/graphql/resolvers/ci/config_resolver.rb
@@ -18,38 +18,49 @@ module Resolvers
required: true,
description: "Contents of '.gitlab-ci.yml'."
- def resolve(project_path:, content:)
- project = authorized_find!(project_path: project_path)
+ argument :dry_run, GraphQL::BOOLEAN_TYPE,
+ required: false,
+ description: 'Run pipeline creation simulation, or only do static check.'
- result = ::Gitlab::Ci::YamlProcessor.new(content, project: project,
- user: current_user,
- sha: project.repository.commit.sha).execute
+ def resolve(project_path:, content:, dry_run: false)
+ project = authorized_find!(project_path: project_path)
- response = if result.errors.empty?
- {
- status: :valid,
- errors: [],
- stages: make_stages(result.jobs)
- }
- else
- {
- status: :invalid,
- errors: result.errors
- }
- end
+ result = ::Gitlab::Ci::Lint
+ .new(project: project, current_user: context[:current_user])
+ .validate(content, dry_run: dry_run)
- response.merge(merged_yaml: result.merged_yaml)
+ if result.errors.empty?
+ {
+ status: :valid,
+ errors: [],
+ stages: make_stages(result.jobs)
+ }
+ else
+ {
+ status: :invalid,
+ errors: result.errors
+ }
+ end
end
private
def make_jobs(config_jobs)
- config_jobs.map do |job_name, job|
+ config_jobs.map do |job|
{
- name: job_name,
+ name: job[:name],
stage: job[:stage],
- group_name: CommitStatus.new(name: job_name).group_name,
- needs: job.dig(:needs, :job) || []
+ group_name: CommitStatus.new(name: job[:name]).group_name,
+ needs: job.dig(:needs) || [],
+ allow_failure: job[:allow_failure],
+ before_script: job[:before_script],
+ script: job[:script],
+ after_script: job[:after_script],
+ only: job[:only],
+ except: job[:except],
+ when: job[:when],
+ tags: job[:tag_list],
+ environment: job[:environment]
}
end
end
diff --git a/app/graphql/types/ci/config/job_restriction_type.rb b/app/graphql/types/ci/config/job_restriction_type.rb
new file mode 100644
index 00000000000..294e3c94571
--- /dev/null
+++ b/app/graphql/types/ci/config/job_restriction_type.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ # rubocop: disable Graphql/AuthorizeTypes
+ module Config
+ class JobRestrictionType < BaseObject
+ graphql_name 'CiConfigJobRestriction'
+
+ field :refs, [GraphQL::STRING_TYPE], null: true,
+ description: 'The Git refs the job restriction applies to.'
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/ci/config/job_type.rb b/app/graphql/types/ci/config/job_type.rb
index 855875ba300..65fdc4c2615 100644
--- a/app/graphql/types/ci/config/job_type.rb
+++ b/app/graphql/types/ci/config/job_type.rb
@@ -8,13 +8,36 @@ module Types
graphql_name 'CiConfigJob'
field :name, GraphQL::STRING_TYPE, null: true,
- description: 'Name of the job'
+ description: 'Name of the job.'
field :group_name, GraphQL::STRING_TYPE, null: true,
- description: 'Name of the job group'
+ description: 'Name of the job group.'
field :stage, GraphQL::STRING_TYPE, null: true,
- description: 'Name of the job stage'
+ description: 'Name of the job stage.'
field :needs, Types::Ci::Config::NeedType.connection_type, null: true,
- description: 'Builds that must complete before the jobs run'
+ description: 'Builds that must complete before the jobs run.'
+ field :allow_failure, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Allow job to fail.'
+ field :before_script, [GraphQL::STRING_TYPE], null: true,
+ description: 'Override a set of commands that are executed before the job.'
+ field :script, [GraphQL::STRING_TYPE], null: true,
+ description: 'Shell script that is executed by a runner.'
+ field :after_script, [GraphQL::STRING_TYPE], null: true,
+ description: 'Override a set of commands that are executed after the job.'
+ field :when, GraphQL::STRING_TYPE, null: true,
+ description: 'When to run the job.',
+ resolver_method: :restrict_when_to_run_jobs
+ field :environment, GraphQL::STRING_TYPE, null: true,
+ description: 'Name of an environment to which the job deploys.'
+ field :except, Types::Ci::Config::JobRestrictionType, null: true,
+ description: 'Limit when jobs are not created.'
+ field :only, Types::Ci::Config::JobRestrictionType, null: true,
+ description: 'Jobs are created when these conditions do not apply.'
+ field :tags, [GraphQL::STRING_TYPE], null: true,
+ description: 'List of tags that are used to select a runner.'
+
+ def restrict_when_to_run_jobs
+ object[:when]
+ end
end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 6a5d69f2e73..56f33ed1e1d 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -428,10 +428,6 @@ class Commit
end
def has_been_reverted?(current_user, notes_association = nil)
- reverting_commit(current_user, notes_association).present?
- end
-
- def reverting_commit(current_user, notes_association = nil)
ext = Gitlab::ReferenceExtractor.new(project, current_user)
notes_association ||= notes_with_associations
@@ -439,7 +435,7 @@ class Commit
note.all_references(current_user, extractor: ext)
end
- ext.commits.find { |commit_ref| commit_ref.reverts_commit?(self, current_user) }
+ ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self, current_user) }
end
def change_type_title(user)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 3918498f694..64b8223a1f0 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1593,26 +1593,6 @@ class MergeRequest < ApplicationRecord
!merge_commit.has_been_reverted?(current_user, notes_association)
end
- def reverted_by_merge_request?(current_user)
- reverting_merge_request(current_user).present?
- end
-
- def reverting_merge_request(current_user)
- return unless merge_commit
- return unless merged_at
-
- reverting_commit = merge_commit.reverting_commit(current_user, notes_with_associations)
-
- if reverting_commit
- MergeRequestsFinder.new(
- current_user,
- project_id: project.id,
- commit_sha: reverting_commit.sha,
- state: 'merged'
- ).execute.first
- end
- end
-
def merged_at
strong_memoize(:merged_at) do
next unless merged?
diff --git a/app/serializers/merge_request_poll_cached_widget_entity.rb b/app/serializers/merge_request_poll_cached_widget_entity.rb
index df7c7d2defa..1db4ec37d4a 100644
--- a/app/serializers/merge_request_poll_cached_widget_entity.rb
+++ b/app/serializers/merge_request_poll_cached_widget_entity.rb
@@ -114,14 +114,6 @@ class MergeRequestPollCachedWidgetEntity < IssuableEntity
end
end
- expose :reverted do |merge_request|
- merge_request.reverted_by_merge_request?(current_user)
- end
-
- expose :reverted_path, if: -> (mr) { mr.reverted_by_merge_request?(current_user) } do |merge_request|
- merge_request_path(merge_request.reverting_merge_request(current_user))
- end
-
private
delegate :current_user, to: :request
diff --git a/app/services/feature_flags/base_service.rb b/app/services/feature_flags/base_service.rb
index 9b27df90992..c11c465252e 100644
--- a/app/services/feature_flags/base_service.rb
+++ b/app/services/feature_flags/base_service.rb
@@ -6,6 +6,11 @@ module FeatureFlags
AUDITABLE_ATTRIBUTES = %w(name description active).freeze
+ def success(**args)
+ sync_to_jira(args[:feature_flag])
+ super
+ end
+
protected
def audit_event(feature_flag)
@@ -34,6 +39,16 @@ module FeatureFlags
audit_event.security_event
end
+ def sync_to_jira(feature_flag)
+ return unless feature_flag.present?
+ return unless Feature.enabled?(:jira_sync_feature_flags, feature_flag.project)
+
+ seq_id = ::Atlassian::JiraConnect::Client.generate_update_sequence_id
+ feature_flag.run_after_commit do
+ ::JiraConnect::SyncFeatureFlagsWorker.perform_async(feature_flag.id, seq_id)
+ end
+ end
+
def created_scope_message(scope)
"Created rule <strong>#{scope.environment_scope}</strong> "\
"and set it as <strong>#{scope.active ? "active" : "inactive"}</strong> "\
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index e3fd9d4bb17..25b7fa2a718 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -6,20 +6,28 @@
.js-remove-member-modal
.project-members-page.gl-mt-3
- %h4
- = _('Group members')
- %hr
- - if can_manage_members
- %ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
- %li.nav-tab{ role: 'presentation' }
- %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' }= _('Invite member')
+ .gl-display-flex.gl-flex-wrap
+ - if can_manage_members
+ .gl-w-half.gl-xs-w-full
+ %h4
+ = _('Group members')
+ - if invite_members_allowed?(@group)
+ .gl-w-half.gl-xs-w-full
+ .gl-display-flex.gl-flex-wrap.gl-lg-justify-content-end.gl-mx-n2.gl-mb-3
+ .js-invite-members-trigger.gl-px-2.gl-sm-w-auto.gl-w-full.gl-mb-4{ data: { classes: 'btn btn-success gl-button gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite members') } }
+ = render_if_exists 'groups/invite_members_modal', group: @group
+ - if can_manage_members && !invite_members_allowed?(@group)
+ %hr.gl-mt-4
+ %ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }
- %a.nav-link{ href: '#invite-group-pane', id: 'invite-group-tab', data: { toggle: 'tab', qa_selector: 'invite_group_tab' }, role: 'tab' }= _('Invite group')
- .tab-content.gitlab-tab-content
- .tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
- = render_invite_member_for_group(@group, @group_member.access_level)
- .tab-pane{ id: 'invite-group-pane', role: 'tabpanel' }
- = render 'shared/members/invite_group', submit_url: group_group_links_path(@group), access_levels: GroupMember.access_level_roles, default_access_level: @group_member.access_level, group_link_field: 'shared_with_group_id', group_access_field: 'shared_group_access'
+ %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' }= _('Invite member')
+ %li.nav-tab{ role: 'presentation' }
+ %a.nav-link{ href: '#invite-group-pane', id: 'invite-group-tab', data: { toggle: 'tab', qa_selector: 'invite_group_tab' }, role: 'tab' }= _('Invite group')
+ .tab-content.gitlab-tab-content
+ .tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
+ = render_invite_member_for_group(@group, @group_member.access_level)
+ .tab-pane{ id: 'invite-group-pane', role: 'tabpanel' }
+ = render 'shared/members/invite_group', submit_url: group_group_links_path(@group), access_levels: GroupMember.access_level_roles, default_access_level: @group_member.access_level, group_link_field: 'shared_with_group_id', group_access_field: 'shared_group_access'
= render_if_exists 'groups/group_members/ldap_sync'
diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml
index 178a9d3f8b4..9f850842f58 100644
--- a/app/views/profiles/two_factor_auths/_codes.html.haml
+++ b/app/views/profiles/two_factor_auths/_codes.html.haml
@@ -1,18 +1,3 @@
- show_success_alert = local_assigns.fetch(:show_success_alert, nil)
-- if Feature.enabled?(:vue_2fa_recovery_codes, current_user, default_enabled: true)
- .js-2fa-recovery-codes{ data: { codes: @codes.to_json, profile_account_path: profile_account_path(two_factor_auth_enabled_successfully: show_success_alert) } }
-- else
- %p.slead
- - lose_2fa_message = _('Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account.') % { b_start:'<b>', b_end:'</b>' }
- = lose_2fa_message.html_safe
-
- .codes.card{ data: { qa_selector: 'codes_content' } }
- %ul
- - @codes.each do |code|
- %li
- %span.monospace{ data: { qa_selector: 'code_content' } }= code
-
- .d-flex
- = link_to _('Proceed'), profile_account_path, class: 'gl-button btn btn-success gl-mr-3', data: { qa_selector: 'proceed_button' }
- = link_to _('Download codes'), "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'gl-button btn btn-default'
+.js-2fa-recovery-codes{ data: { codes: @codes.to_json, profile_account_path: profile_account_path(two_factor_auth_enabled_successfully: show_success_alert) } }
diff --git a/app/views/profiles/two_factor_auths/create.html.haml b/app/views/profiles/two_factor_auths/create.html.haml
index be4800024cf..606dda5ed55 100644
--- a/app/views/profiles/two_factor_auths/create.html.haml
+++ b/app/views/profiles/two_factor_auths/create.html.haml
@@ -1,8 +1,4 @@
- page_title _('Two-factor Authentication'), _('Account')
- add_page_specific_style 'page_bundles/profile_two_factor_auth'
-- unless Feature.enabled?(:vue_2fa_recovery_codes, current_user, default_enabled: true)
- .gl-alert.gl-alert-success.gl-mb-5
- = _('Congratulations! You have enabled Two-factor Authentication!')
-
= render 'codes', show_success_alert: true
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 2d7218740ef..4711143c900 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -40,12 +40,11 @@
.issuable-meta
%ul.controls.d-flex.align-items-end
- %li.issuable-status.d-none.d-sm-inline-block
- - if merge_request.reverted_by_merge_request?(current_user)
- = _('MERGED (REVERTED)')
- - elsif merge_request.merged?
+ - if merge_request.merged?
+ %li.issuable-status.d-none.d-sm-inline-block
= _('MERGED')
- - elsif merge_request.closed?
+ - elsif merge_request.closed?
+ %li.issuable-status.d-none.d-sm-inline-block
= sprite_icon('cancel', css_class: 'gl-vertical-align-text-bottom')
= _('CLOSED')
= render 'shared/merge_request_pipeline_status', merge_request: merge_request
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index c423df49b2b..6a42f33db7d 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -3,8 +3,6 @@
- can_reopen_merge_request = can?(current_user, :reopen_merge_request, @merge_request)
- state_human_name, state_icon_name = state_name_with_icon(@merge_request)
- are_close_and_open_buttons_hidden = merge_request_button_hidden?(@merge_request, true) && merge_request_button_hidden?(@merge_request, false)
-- is_reverted = @merge_request.reverted_by_merge_request?(current_user)
-- reverted_mr_path = is_reverted ? merge_request_path(@merge_request.reverting_merge_request(current_user)) : nil
- if @merge_request.closed_or_merged_without_fork?
.gl-alert.gl-alert-danger.gl-mb-5
@@ -14,13 +12,10 @@
.detail-page-header.border-bottom-0.pt-0.pb-0
.detail-page-header-body
- .issuable-status-box.status-box.js-mr-status-box{ class: status_box_class(@merge_request), data: { state: @merge_request.state, is_reverted: is_reverted.to_s, reverted_path: reverted_mr_path } }
+ .issuable-status-box.status-box.js-mr-status-box{ class: status_box_class(@merge_request), data: { state: @merge_request.state } }
= sprite_icon(state_icon_name, css_class: 'gl-display-block gl-display-sm-none!')
%span.gl-display-none.gl-display-sm-block
- - if @merge_request.reverted_by_merge_request?(current_user)
- = _('Merged (%{reverted})').html_safe % { reverted: link_to(s_('MergeRequest|reverted'), reverted_mr_path, class: 'gl-reset-color! gl-text-decoration-underline') }
- - else
- = state_human_name
+ = state_human_name
.issuable-meta
#js-issuable-header-warnings
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 0f5f169f548..cf39ac4dd56 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -4,16 +4,34 @@
.js-remove-member-modal
.row.gl-mt-3
.col-lg-12
- - if project_can_be_shared?
- %h4
- = _("Project members")
- - if can_manage_project_members?(@project)
- %p= share_project_description(@project)
- - else
- %p
- = html_escape(_("Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}")) % { i_open: '<i>'.html_safe, i_close: '</i>'.html_safe }
+ - if invite_members_allowed?(group)
+ .row
+ .col-md-12.col-lg-6.gl-display-flex
+ .gl-flex-direction-column.gl-flex-wrap.align-items-baseline
+ %h4
+ = _("Project members")
+ .gl-justify-content-bottom.gl-display-flex.align-items-center
+ - if can_manage_project_members?(@project)
+ %p= share_project_description(@project)
+ - else
+ %p
+ = html_escape(_("Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}")) % { i_open: '<i>'.html_safe, i_close: '</i>'.html_safe }
+ .col-md-12.col-lg-6
+ .gl-display-flex.gl-flex-wrap.gl-lg-justify-content-end.gl-mx-n2.gl-mb-3
+ .js-invite-members-trigger.gl-px-2.gl-sm-w-auto.gl-w-full.gl-mb-4{ data: { classes: 'btn btn-success gl-button gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite members') } }
+ = render_if_exists 'projects/invite_members_modal', project: @project
- - if can_manage_project_members?(@project) && project_can_be_shared?
+ - else
+ - if project_can_be_shared?
+ %h4
+ = _("Project members")
+ - if can_manage_project_members?(@project)
+ %p= share_project_description(@project)
+ - else
+ %p
+ = html_escape(_("Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}")) % { i_open: '<i>'.html_safe, i_close: '</i>'.html_safe }
+
+ - if !invite_members_allowed?(group) && can_manage_project_members?(@project) && project_can_be_shared?
- if !membership_locked? && @project.allowed_to_share_with_group?
%ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 7bc4818b625..55b6cf372fb 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -45,6 +45,17 @@
.settings-content
= render 'projects/runners/index'
+%section.settings.no-animate#js-artifacts-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _("Artifacts")
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _("A job artifact is an archive of files and directories saved by a job when it finishes.")
+ .settings-content
+ #js-artifacts-settings-app{ data: { full_path: @project.full_path, help_page_path: help_page_path('ci/pipelines/job_artifacts', anchor: 'keep-artifacts-from-most-recent-successful-jobs') } }
+
%section.qa-variables-settings.settings.no-animate#js-cicd-variables-settings{ class: ('expanded' if expanded), data: { qa_selector: 'variables_settings_content' } }
.settings-header
= render 'ci/variables/header', expanded: expanded
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 05a41fceafe..9ac10aa2d7c 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -907,6 +907,14 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: jira_connect:jira_connect_sync_feature_flags
+ :feature_category: :integrations
+ :has_external_dependencies: true
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: jira_connect:jira_connect_sync_merge_request
:feature_category: :integrations
:has_external_dependencies: true
diff --git a/app/workers/jira_connect/sync_feature_flags_worker.rb b/app/workers/jira_connect/sync_feature_flags_worker.rb
new file mode 100644
index 00000000000..7e98d0eada7
--- /dev/null
+++ b/app/workers/jira_connect/sync_feature_flags_worker.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module JiraConnect
+ class SyncFeatureFlagsWorker
+ include ApplicationWorker
+
+ idempotent!
+ worker_has_external_dependencies!
+
+ queue_namespace :jira_connect
+ feature_category :integrations
+
+ def perform(feature_flag_id, sequence_id)
+ feature_flag = ::Operations::FeatureFlag.find_by_id(feature_flag_id)
+
+ return unless feature_flag
+ return unless Feature.enabled?(:jira_sync_feature_flags, feature_flag.project)
+
+ ::JiraConnect::SyncService
+ .new(feature_flag.project)
+ .execute(feature_flags: [feature_flag], update_sequence_id: sequence_id)
+ end
+ end
+end