Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d49d44c810
commit
6fa3630aad
|
@ -15,9 +15,6 @@ tasks:
|
||||||
cd /workspace/gitlab-development-kit
|
cd /workspace/gitlab-development-kit
|
||||||
[[ ! -L /workspace/gitlab-development-kit/gitlab ]] && ln -fs /workspace/gitlab /workspace/gitlab-development-kit/gitlab
|
[[ ! -L /workspace/gitlab-development-kit/gitlab ]] && ln -fs /workspace/gitlab /workspace/gitlab-development-kit/gitlab
|
||||||
mv /workspace/gitlab-development-kit/secrets.yml /workspace/gitlab-development-kit/gitlab/config
|
mv /workspace/gitlab-development-kit/secrets.yml /workspace/gitlab-development-kit/gitlab/config
|
||||||
# make webpack static, prevents that GitLab tries to connect to localhost webpack from browser outside the workspace
|
|
||||||
echo "webpack:" >> gdk.yml
|
|
||||||
echo " static: true" >> gdk.yml
|
|
||||||
# reconfigure GDK
|
# reconfigure GDK
|
||||||
echo "$(date) – Reconfiguring GDK" | tee -a /workspace/startup.log
|
echo "$(date) – Reconfiguring GDK" | tee -a /workspace/startup.log
|
||||||
gdk reconfigure
|
gdk reconfigure
|
||||||
|
@ -43,6 +40,7 @@ tasks:
|
||||||
fi
|
fi
|
||||||
# start GDK
|
# start GDK
|
||||||
echo "$(date) – Starting GDK" | tee -a /workspace/startup.log
|
echo "$(date) – Starting GDK" | tee -a /workspace/startup.log
|
||||||
|
export DEV_SERVER_PUBLIC_ADDR=$(gp url 3808)
|
||||||
export RAILS_HOSTS=$(gp url 3000 | sed -e 's+^http[s]*://++')
|
export RAILS_HOSTS=$(gp url 3000 | sed -e 's+^http[s]*://++')
|
||||||
gdk start
|
gdk start
|
||||||
# Run DB migrations
|
# Run DB migrations
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlDropdown, GlDropdownItem, GlIcon, GlLink, GlModal } from '@gitlab/ui';
|
import { GlButton, GlDropdown, GlDropdownItem, GlIcon, GlLink, GlModal } from '@gitlab/ui';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import createFlash from '~/flash';
|
import createFlash, { FLASH_TYPES } from '~/flash';
|
||||||
import { IssuableType } from '~/issuable_show/constants';
|
import { IssuableType } from '~/issuable_show/constants';
|
||||||
import { IssuableStatus, IssueStateEvent } from '~/issue_show/constants';
|
import { IssuableStatus, IssueStateEvent } from '~/issue_show/constants';
|
||||||
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
|
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
|
||||||
|
import { visitUrl } from '~/lib/utils/url_utility';
|
||||||
import { __, sprintf } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
|
import promoteToEpicMutation from '../queries/promote_to_epic.mutation.graphql';
|
||||||
import updateIssueMutation from '../queries/update_issue.mutation.graphql';
|
import updateIssueMutation from '../queries/update_issue.mutation.graphql';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -24,10 +26,21 @@ export default {
|
||||||
text: __('Yes, close issue'),
|
text: __('Yes, close issue'),
|
||||||
attributes: [{ variant: 'warning' }],
|
attributes: [{ variant: 'warning' }],
|
||||||
},
|
},
|
||||||
|
i18n: {
|
||||||
|
promoteErrorMessage: __(
|
||||||
|
'Something went wrong while promoting the issue to an epic. Please try again.',
|
||||||
|
),
|
||||||
|
promoteSuccessMessage: __(
|
||||||
|
'The issue was successfully promoted to an epic. Redirecting to epic...',
|
||||||
|
),
|
||||||
|
},
|
||||||
inject: {
|
inject: {
|
||||||
canCreateIssue: {
|
canCreateIssue: {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
canPromoteToEpic: {
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
canReopenIssue: {
|
canReopenIssue: {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
@ -135,6 +148,37 @@ export default {
|
||||||
this.isUpdatingState = false;
|
this.isUpdatingState = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
promoteToEpic() {
|
||||||
|
this.isUpdatingState = true;
|
||||||
|
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: promoteToEpicMutation,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
iid: this.iid,
|
||||||
|
projectPath: this.projectPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ data }) => {
|
||||||
|
if (data.promoteToEpic.errors.length) {
|
||||||
|
createFlash({ message: data.promoteToEpic.errors.join('; ') });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
createFlash({
|
||||||
|
message: this.$options.i18n.promoteSuccessMessage,
|
||||||
|
type: FLASH_TYPES.SUCCESS,
|
||||||
|
});
|
||||||
|
|
||||||
|
visitUrl(data.promoteToEpic.epic.webPath);
|
||||||
|
})
|
||||||
|
.catch(() => createFlash({ message: this.$options.i18n.promoteErrorMessage }))
|
||||||
|
.finally(() => {
|
||||||
|
this.isUpdatingState = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -152,6 +196,9 @@ export default {
|
||||||
<gl-dropdown-item v-if="canCreateIssue" :href="newIssuePath">
|
<gl-dropdown-item v-if="canCreateIssue" :href="newIssuePath">
|
||||||
{{ newIssueTypeText }}
|
{{ newIssueTypeText }}
|
||||||
</gl-dropdown-item>
|
</gl-dropdown-item>
|
||||||
|
<gl-dropdown-item v-if="canPromoteToEpic" :disabled="isUpdatingState" @click="promoteToEpic">
|
||||||
|
{{ __('Promote to epic') }}
|
||||||
|
</gl-dropdown-item>
|
||||||
<gl-dropdown-item v-if="!isIssueAuthor" :href="reportAbusePath">
|
<gl-dropdown-item v-if="!isIssueAuthor" :href="reportAbusePath">
|
||||||
{{ __('Report abuse') }}
|
{{ __('Report abuse') }}
|
||||||
</gl-dropdown-item>
|
</gl-dropdown-item>
|
||||||
|
@ -190,6 +237,14 @@ export default {
|
||||||
<gl-dropdown-item v-if="canCreateIssue" :href="newIssuePath">
|
<gl-dropdown-item v-if="canCreateIssue" :href="newIssuePath">
|
||||||
{{ newIssueTypeText }}
|
{{ newIssueTypeText }}
|
||||||
</gl-dropdown-item>
|
</gl-dropdown-item>
|
||||||
|
<gl-dropdown-item
|
||||||
|
v-if="canPromoteToEpic"
|
||||||
|
:disabled="isUpdatingState"
|
||||||
|
data-testid="promote-button"
|
||||||
|
@click="promoteToEpic"
|
||||||
|
>
|
||||||
|
{{ __('Promote to epic') }}
|
||||||
|
</gl-dropdown-item>
|
||||||
<gl-dropdown-item v-if="!isIssueAuthor" :href="reportAbusePath">
|
<gl-dropdown-item v-if="!isIssueAuthor" :href="reportAbusePath">
|
||||||
{{ __('Report abuse') }}
|
{{ __('Report abuse') }}
|
||||||
</gl-dropdown-item>
|
</gl-dropdown-item>
|
||||||
|
|
|
@ -45,6 +45,7 @@ export function initIssueHeaderActions(store) {
|
||||||
store,
|
store,
|
||||||
provide: {
|
provide: {
|
||||||
canCreateIssue: parseBoolean(el.dataset.canCreateIssue),
|
canCreateIssue: parseBoolean(el.dataset.canCreateIssue),
|
||||||
|
canPromoteToEpic: parseBoolean(el.dataset.canPromoteToEpic),
|
||||||
canReopenIssue: parseBoolean(el.dataset.canReopenIssue),
|
canReopenIssue: parseBoolean(el.dataset.canReopenIssue),
|
||||||
canReportSpam: parseBoolean(el.dataset.canReportSpam),
|
canReportSpam: parseBoolean(el.dataset.canReportSpam),
|
||||||
canUpdateIssue: parseBoolean(el.dataset.canUpdateIssue),
|
canUpdateIssue: parseBoolean(el.dataset.canUpdateIssue),
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
mutation promoteToEpic($input: PromoteToEpicInput!) {
|
||||||
|
promoteToEpic(input: $input) {
|
||||||
|
epic {
|
||||||
|
webPath
|
||||||
|
}
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,6 @@ import UsersSelect from '~/users_select';
|
||||||
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
||||||
import initBoards from '~/boards';
|
import initBoards from '~/boards';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
new UsersSelect(); // eslint-disable-line no-new
|
||||||
new UsersSelect(); // eslint-disable-line no-new
|
new ShortcutsNavigation(); // eslint-disable-line no-new
|
||||||
new ShortcutsNavigation(); // eslint-disable-line no-new
|
initBoards();
|
||||||
initBoards();
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import initForm from '../../../../shared/milestones/form';
|
import initForm from '../../../../shared/milestones/form';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => initForm(false));
|
initForm(false);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import initForm from '../../../../shared/milestones/form';
|
import initForm from '../../../../shared/milestones/form';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => initForm(false));
|
initForm(false);
|
||||||
|
|
|
@ -4,13 +4,11 @@ import Group from '~/group';
|
||||||
import GroupPathValidator from './group_path_validator';
|
import GroupPathValidator from './group_path_validator';
|
||||||
import initFilePickers from '~/file_pickers';
|
import initFilePickers from '~/file_pickers';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
const parentId = $('#group_parent_id');
|
||||||
const parentId = $('#group_parent_id');
|
if (!parentId.val()) {
|
||||||
if (!parentId.val()) {
|
new GroupPathValidator(); // eslint-disable-line no-new
|
||||||
new GroupPathValidator(); // eslint-disable-line no-new
|
}
|
||||||
}
|
BindInOut.initAll();
|
||||||
BindInOut.initAll();
|
initFilePickers();
|
||||||
initFilePickers();
|
|
||||||
|
|
||||||
return new Group();
|
new Group(); // eslint-disable-line no-new
|
||||||
});
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import initPackageList from '~/packages/list/packages_list_app_bundle';
|
import initPackageList from '~/packages/list/packages_list_app_bundle';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
if (document.getElementById('js-vue-packages-list')) {
|
||||||
if (document.getElementById('js-vue-packages-list')) {
|
initPackageList();
|
||||||
initPackageList();
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
|
@ -141,10 +141,9 @@ export default {
|
||||||
addMultipleToDiscussionWarning() {
|
addMultipleToDiscussionWarning() {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
__(
|
__(
|
||||||
'%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification.',
|
'You are about to add %{usersTag} people to the discussion. They will all receive a notification.',
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
icon: '<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>',
|
|
||||||
usersTag: `<strong><span class="js-referenced-users-count">${this.referencedUsers.length}</span></strong>`,
|
usersTag: `<strong><span class="js-referenced-users-count">${this.referencedUsers.length}</span></strong>`,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
|
@ -293,6 +292,7 @@ export default {
|
||||||
<template v-if="previewMarkdown && !markdownPreviewLoading">
|
<template v-if="previewMarkdown && !markdownPreviewLoading">
|
||||||
<div v-if="referencedCommands" class="referenced-commands" v-html="referencedCommands"></div>
|
<div v-if="referencedCommands" class="referenced-commands" v-html="referencedCommands"></div>
|
||||||
<div v-if="shouldShowReferencedUsers" class="referenced-users">
|
<div v-if="shouldShowReferencedUsers" class="referenced-users">
|
||||||
|
<gl-icon name="warning-solid" />
|
||||||
<span v-html="addMultipleToDiscussionWarning"></span>
|
<span v-html="addMultipleToDiscussionWarning"></span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -201,6 +201,15 @@ $line-removed-dark: $red-200;
|
||||||
// Misc component overrides that should live elsewhere
|
// Misc component overrides that should live elsewhere
|
||||||
.gl-label {
|
.gl-label {
|
||||||
filter: brightness(0.9) contrast(1.1);
|
filter: brightness(0.9) contrast(1.1);
|
||||||
|
|
||||||
|
// This applies to the gl-label markups
|
||||||
|
// rendered and cached in the backend (labels_helper.rb)
|
||||||
|
&.gl-label-scoped {
|
||||||
|
.gl-label-text-scoped,
|
||||||
|
.gl-label-close {
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// white-ish text for light labels
|
// white-ish text for light labels
|
||||||
|
@ -210,6 +219,15 @@ $line-removed-dark: $red-200;
|
||||||
color: $gray-900;
|
color: $gray-900;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This applies to "gl-labels" from "gitlab-ui"
|
||||||
|
.gl-label.gl-label-scoped.gl-label-text-dark,
|
||||||
|
.gl-label.gl-label-scoped.gl-label-text-light {
|
||||||
|
.gl-label-text-scoped,
|
||||||
|
.gl-label-close {
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// duplicated class as the original .atwho-view style is added later
|
// duplicated class as the original .atwho-view style is added later
|
||||||
.atwho-view.atwho-view {
|
.atwho-view.atwho-view {
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Projects::PipelinesController < Projects::ApplicationController
|
||||||
before_action do
|
before_action do
|
||||||
push_frontend_feature_flag(:dag_pipeline_tab, project, default_enabled: true)
|
push_frontend_feature_flag(:dag_pipeline_tab, project, default_enabled: true)
|
||||||
push_frontend_feature_flag(:pipelines_security_report_summary, project)
|
push_frontend_feature_flag(:pipelines_security_report_summary, project)
|
||||||
push_frontend_feature_flag(:new_pipeline_form, project)
|
push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: true)
|
||||||
push_frontend_feature_flag(:graphql_pipeline_header, project, type: :development, default_enabled: false)
|
push_frontend_feature_flag(:graphql_pipeline_header, project, type: :development, default_enabled: false)
|
||||||
push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: false)
|
push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: false)
|
||||||
push_frontend_feature_flag(:new_pipeline_form_prefilled_vars, project, type: :development)
|
push_frontend_feature_flag(:new_pipeline_form_prefilled_vars, project, type: :development)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Types
|
||||||
|
class ContainerRepositoryCleanupStatusEnum < BaseEnum
|
||||||
|
graphql_name 'ContainerRepositoryCleanupStatus'
|
||||||
|
description 'Status of the tags cleanup of a container repository'
|
||||||
|
|
||||||
|
value 'UNSCHEDULED', value: 'cleanup_unscheduled', description: 'The tags cleanup is not scheduled. This is the default state.'
|
||||||
|
value 'SCHEDULED', value: 'cleanup_scheduled', description: 'The tags cleanup is scheduled and is going to be executed shortly.'
|
||||||
|
value 'UNFINISHED', value: 'cleanup_unfinished', description: 'The tags cleanup has been partially executed. There are still remaining tags to delete.'
|
||||||
|
value 'ONGOING', value: 'cleanup_ongoing', description: 'The tags cleanup is ongoing.'
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,6 +15,7 @@ module Types
|
||||||
field :created_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was created.'
|
field :created_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was created.'
|
||||||
field :updated_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was updated.'
|
field :updated_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was updated.'
|
||||||
field :expiration_policy_started_at, Types::TimeType, null: true, description: 'Timestamp when the cleanup done by the expiration policy was started on the container repository.'
|
field :expiration_policy_started_at, Types::TimeType, null: true, description: 'Timestamp when the cleanup done by the expiration policy was started on the container repository.'
|
||||||
|
field :expiration_policy_cleanup_status, Types::ContainerRepositoryCleanupStatusEnum, null: true, description: 'The tags cleanup status for the container repository.'
|
||||||
field :status, Types::ContainerRepositoryStatusEnum, null: true, description: 'Status of the container repository.'
|
field :status, Types::ContainerRepositoryStatusEnum, null: true, description: 'Status of the container repository.'
|
||||||
field :tags_count, GraphQL::INT_TYPE, null: false, description: 'Number of tags associated with this image.'
|
field :tags_count, GraphQL::INT_TYPE, null: false, description: 'Number of tags associated with this image.'
|
||||||
field :can_delete, GraphQL::BOOLEAN_TYPE, null: false, description: 'Can the current user delete the container repository.'
|
field :can_delete, GraphQL::BOOLEAN_TYPE, null: false, description: 'Can the current user delete the container repository.'
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
.dropdown.inline.js-ca-dropdown
|
.dropdown.inline.js-ca-dropdown
|
||||||
%button.dropdown-menu-toggle{ "data-toggle" => "dropdown", :type => "button" }
|
%button.dropdown-menu-toggle{ "data-toggle" => "dropdown", :type => "button" }
|
||||||
%span.dropdown-label {{ n__('Last %d day', 'Last %d days', 30) }}
|
%span.dropdown-label {{ n__('Last %d day', 'Last %d days', 30) }}
|
||||||
%i.fa.fa-chevron-down
|
= sprite_icon("chevron-down", css_class: "dropdown-menu-toggle-icon gl-top-3")
|
||||||
%ul.dropdown-menu.dropdown-menu-right
|
%ul.dropdown-menu.dropdown-menu-right
|
||||||
%li
|
%li
|
||||||
%a{ "href" => "#", "data-value" => "7" }
|
%a{ "href" => "#", "data-value" => "7" }
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
= s_('Pipeline|Run Pipeline')
|
= s_('Pipeline|Run Pipeline')
|
||||||
%hr
|
%hr
|
||||||
|
|
||||||
- if Feature.enabled?(:new_pipeline_form, @project)
|
- if Feature.enabled?(:new_pipeline_form, @project, default_enabled: true)
|
||||||
#js-new-pipeline{ data: { project_id: @project.id,
|
#js-new-pipeline{ data: { project_id: @project.id,
|
||||||
pipelines_path: project_pipelines_path(@project),
|
pipelines_path: project_pipelines_path(@project),
|
||||||
config_variables_path: config_variables_namespace_project_pipelines_path(@project.namespace, @project),
|
config_variables_path: config_variables_namespace_project_pipelines_path(@project.namespace, @project),
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add cleanup status field to graphQL ContainerRepositoryType
|
||||||
|
merge_request: 47544
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Replace fa-chevron-down in project level VSA
|
||||||
|
merge_request: 47885
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Replace fa-exclamation-triangle in markdown field MERGE_REQUEST_ID
|
||||||
|
merge_request: 47786
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Default enable new_pipeline_form
|
||||||
|
merge_request: 46915
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/229632
|
||||||
milestone: '13.2'
|
milestone: '13.2'
|
||||||
type: development
|
type: development
|
||||||
group: group::continuous integration
|
group: group::continuous integration
|
||||||
default_enabled: false
|
default_enabled: true
|
||||||
|
|
|
@ -18,6 +18,7 @@ const IS_DEV_SERVER = process.env.WEBPACK_DEV_SERVER === 'true';
|
||||||
const IS_EE = require('./helpers/is_ee_env');
|
const IS_EE = require('./helpers/is_ee_env');
|
||||||
const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
|
const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
|
||||||
const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
|
const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
|
||||||
|
const DEV_SERVER_PUBLIC_ADDR = process.env.DEV_SERVER_PUBLIC_ADDR;
|
||||||
const DEV_SERVER_HTTPS = process.env.DEV_SERVER_HTTPS && process.env.DEV_SERVER_HTTPS !== 'false';
|
const DEV_SERVER_HTTPS = process.env.DEV_SERVER_HTTPS && process.env.DEV_SERVER_HTTPS !== 'false';
|
||||||
const DEV_SERVER_LIVERELOAD = IS_DEV_SERVER && process.env.DEV_SERVER_LIVERELOAD !== 'false';
|
const DEV_SERVER_LIVERELOAD = IS_DEV_SERVER && process.env.DEV_SERVER_LIVERELOAD !== 'false';
|
||||||
const WEBPACK_REPORT = process.env.WEBPACK_REPORT && process.env.WEBPACK_REPORT !== 'false';
|
const WEBPACK_REPORT = process.env.WEBPACK_REPORT && process.env.WEBPACK_REPORT !== 'false';
|
||||||
|
@ -554,6 +555,7 @@ module.exports = {
|
||||||
devServer: {
|
devServer: {
|
||||||
host: DEV_SERVER_HOST,
|
host: DEV_SERVER_HOST,
|
||||||
port: DEV_SERVER_PORT,
|
port: DEV_SERVER_PORT,
|
||||||
|
public: DEV_SERVER_PUBLIC_ADDR,
|
||||||
https: DEV_SERVER_HTTPS,
|
https: DEV_SERVER_HTTPS,
|
||||||
headers: {
|
headers: {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
|
|
@ -3313,6 +3313,11 @@ type ContainerRepository {
|
||||||
"""
|
"""
|
||||||
createdAt: Time!
|
createdAt: Time!
|
||||||
|
|
||||||
|
"""
|
||||||
|
The tags cleanup status for the container repository.
|
||||||
|
"""
|
||||||
|
expirationPolicyCleanupStatus: ContainerRepositoryCleanupStatus
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Timestamp when the cleanup done by the expiration policy was started on the container repository.
|
Timestamp when the cleanup done by the expiration policy was started on the container repository.
|
||||||
"""
|
"""
|
||||||
|
@ -3354,6 +3359,31 @@ type ContainerRepository {
|
||||||
updatedAt: Time!
|
updatedAt: Time!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Status of the tags cleanup of a container repository
|
||||||
|
"""
|
||||||
|
enum ContainerRepositoryCleanupStatus {
|
||||||
|
"""
|
||||||
|
The tags cleanup is ongoing.
|
||||||
|
"""
|
||||||
|
ONGOING
|
||||||
|
|
||||||
|
"""
|
||||||
|
The tags cleanup is scheduled and is going to be executed shortly.
|
||||||
|
"""
|
||||||
|
SCHEDULED
|
||||||
|
|
||||||
|
"""
|
||||||
|
The tags cleanup has been partially executed. There are still remaining tags to delete.
|
||||||
|
"""
|
||||||
|
UNFINISHED
|
||||||
|
|
||||||
|
"""
|
||||||
|
The tags cleanup is not scheduled. This is the default state.
|
||||||
|
"""
|
||||||
|
UNSCHEDULED
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The connection type for ContainerRepository.
|
The connection type for ContainerRepository.
|
||||||
"""
|
"""
|
||||||
|
@ -3388,6 +3418,11 @@ type ContainerRepositoryDetails {
|
||||||
"""
|
"""
|
||||||
createdAt: Time!
|
createdAt: Time!
|
||||||
|
|
||||||
|
"""
|
||||||
|
The tags cleanup status for the container repository.
|
||||||
|
"""
|
||||||
|
expirationPolicyCleanupStatus: ContainerRepositoryCleanupStatus
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Timestamp when the cleanup done by the expiration policy was started on the container repository.
|
Timestamp when the cleanup done by the expiration policy was started on the container repository.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -8947,6 +8947,20 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "expirationPolicyCleanupStatus",
|
||||||
|
"description": "The tags cleanup status for the container repository.",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "ENUM",
|
||||||
|
"name": "ContainerRepositoryCleanupStatus",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "expirationPolicyStartedAt",
|
"name": "expirationPolicyStartedAt",
|
||||||
"description": "Timestamp when the cleanup done by the expiration policy was started on the container repository.",
|
"description": "Timestamp when the cleanup done by the expiration policy was started on the container repository.",
|
||||||
|
@ -9091,6 +9105,41 @@
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "ENUM",
|
||||||
|
"name": "ContainerRepositoryCleanupStatus",
|
||||||
|
"description": "Status of the tags cleanup of a container repository",
|
||||||
|
"fields": null,
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": null,
|
||||||
|
"enumValues": [
|
||||||
|
{
|
||||||
|
"name": "UNSCHEDULED",
|
||||||
|
"description": "The tags cleanup is not scheduled. This is the default state.",
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SCHEDULED",
|
||||||
|
"description": "The tags cleanup is scheduled and is going to be executed shortly.",
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "UNFINISHED",
|
||||||
|
"description": "The tags cleanup has been partially executed. There are still remaining tags to delete.",
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ONGOING",
|
||||||
|
"description": "The tags cleanup is ongoing.",
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "OBJECT",
|
"kind": "OBJECT",
|
||||||
"name": "ContainerRepositoryConnection",
|
"name": "ContainerRepositoryConnection",
|
||||||
|
@ -9199,6 +9248,20 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "expirationPolicyCleanupStatus",
|
||||||
|
"description": "The tags cleanup status for the container repository.",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "ENUM",
|
||||||
|
"name": "ContainerRepositoryCleanupStatus",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "expirationPolicyStartedAt",
|
"name": "expirationPolicyStartedAt",
|
||||||
"description": "Timestamp when the cleanup done by the expiration policy was started on the container repository.",
|
"description": "Timestamp when the cleanup done by the expiration policy was started on the container repository.",
|
||||||
|
|
|
@ -535,6 +535,7 @@ A container repository.
|
||||||
| ----- | ---- | ----------- |
|
| ----- | ---- | ----------- |
|
||||||
| `canDelete` | Boolean! | Can the current user delete the container repository. |
|
| `canDelete` | Boolean! | Can the current user delete the container repository. |
|
||||||
| `createdAt` | Time! | Timestamp when the container repository was created. |
|
| `createdAt` | Time! | Timestamp when the container repository was created. |
|
||||||
|
| `expirationPolicyCleanupStatus` | ContainerRepositoryCleanupStatus | The tags cleanup status for the container repository. |
|
||||||
| `expirationPolicyStartedAt` | Time | Timestamp when the cleanup done by the expiration policy was started on the container repository. |
|
| `expirationPolicyStartedAt` | Time | Timestamp when the cleanup done by the expiration policy was started on the container repository. |
|
||||||
| `id` | ID! | ID of the container repository. |
|
| `id` | ID! | ID of the container repository. |
|
||||||
| `location` | String! | URL of the container repository. |
|
| `location` | String! | URL of the container repository. |
|
||||||
|
@ -552,6 +553,7 @@ Details of a container repository.
|
||||||
| ----- | ---- | ----------- |
|
| ----- | ---- | ----------- |
|
||||||
| `canDelete` | Boolean! | Can the current user delete the container repository. |
|
| `canDelete` | Boolean! | Can the current user delete the container repository. |
|
||||||
| `createdAt` | Time! | Timestamp when the container repository was created. |
|
| `createdAt` | Time! | Timestamp when the container repository was created. |
|
||||||
|
| `expirationPolicyCleanupStatus` | ContainerRepositoryCleanupStatus | The tags cleanup status for the container repository. |
|
||||||
| `expirationPolicyStartedAt` | Time | Timestamp when the cleanup done by the expiration policy was started on the container repository. |
|
| `expirationPolicyStartedAt` | Time | Timestamp when the cleanup done by the expiration policy was started on the container repository. |
|
||||||
| `id` | ID! | ID of the container repository. |
|
| `id` | ID! | ID of the container repository. |
|
||||||
| `location` | String! | URL of the container repository. |
|
| `location` | String! | URL of the container repository. |
|
||||||
|
@ -3812,6 +3814,17 @@ Mode of a commit action.
|
||||||
| `SEVEN_DAYS` | 7 days until tags are automatically removed |
|
| `SEVEN_DAYS` | 7 days until tags are automatically removed |
|
||||||
| `THIRTY_DAYS` | 30 days until tags are automatically removed |
|
| `THIRTY_DAYS` | 30 days until tags are automatically removed |
|
||||||
|
|
||||||
|
### ContainerRepositoryCleanupStatus
|
||||||
|
|
||||||
|
Status of the tags cleanup of a container repository.
|
||||||
|
|
||||||
|
| Value | Description |
|
||||||
|
| ----- | ----------- |
|
||||||
|
| `ONGOING` | The tags cleanup is ongoing. |
|
||||||
|
| `SCHEDULED` | The tags cleanup is scheduled and is going to be executed shortly. |
|
||||||
|
| `UNFINISHED` | The tags cleanup has been partially executed. There are still remaining tags to delete. |
|
||||||
|
| `UNSCHEDULED` | The tags cleanup is not scheduled. This is the default state. |
|
||||||
|
|
||||||
### ContainerRepositoryStatus
|
### ContainerRepositoryStatus
|
||||||
|
|
||||||
Status of a container repository.
|
Status of a container repository.
|
||||||
|
|
|
@ -397,7 +397,7 @@ field :foo, GraphQL::STRING_TYPE,
|
||||||
'if `my_feature_flag` feature flag is disabled'
|
'if `my_feature_flag` feature flag is disabled'
|
||||||
|
|
||||||
def foo
|
def foo
|
||||||
object.foo unless Feature.enabled?(:my_feature_flag, object)
|
object.foo if Feature.enabled?(:my_feature_flag, object)
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,8 @@ job finishes but the DAST job fails, the security dashboard doesn't show SAST re
|
||||||
the analyzer outputs an
|
the analyzer outputs an
|
||||||
[exit code](../../../development/integrations/secure.md#exit-code).
|
[exit code](../../../development/integrations/secure.md#exit-code).
|
||||||
|
|
||||||
|
You can filter the vulnerabilities list by selecting from the **Severity** and **Scanner** dropdowns.
|
||||||
|
|
||||||
## Project Security Dashboard
|
## Project Security Dashboard
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/235558) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.6.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/235558) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.6.
|
||||||
|
@ -105,6 +107,11 @@ You can filter the vulnerabilities by one or more of the following:
|
||||||
| Severity | Critical, High, Medium, Low, Info, Unknown |
|
| Severity | Critical, High, Medium, Low, Info, Unknown |
|
||||||
| Scanner | [Available Scanners](../index.md#security-scanning-tools) |
|
| Scanner | [Available Scanners](../index.md#security-scanning-tools) |
|
||||||
|
|
||||||
|
You can filter the vulnerabilities list by selecting from the **Status**, **Severity**, and
|
||||||
|
**Scanner** dropdowns. In the **Scanner** dropdown, select individual scanners or scanner groups to
|
||||||
|
toggle those scanners. The **Scanner** dropdown includes both GitLab scanners, and in GitLab 13.6
|
||||||
|
and later, custom scanners.
|
||||||
|
|
||||||
You can also dismiss vulnerabilities in the table:
|
You can also dismiss vulnerabilities in the table:
|
||||||
|
|
||||||
1. Select the checkbox for each vulnerability you want to dismiss.
|
1. Select the checkbox for each vulnerability you want to dismiss.
|
||||||
|
@ -260,6 +267,11 @@ You can filter which vulnerabilities the vulnerability report displays by:
|
||||||
| Scanner | [Available Scanners](../index.md#security-scanning-tools) |
|
| Scanner | [Available Scanners](../index.md#security-scanning-tools) |
|
||||||
| Project | Projects configured in the Security Center settings |
|
| Project | Projects configured in the Security Center settings |
|
||||||
|
|
||||||
|
You can filter the vulnerabilities list by selecting from the **Status**, **Severity**, and
|
||||||
|
**Scanner**, and **Project** dropdowns. In the **Scanner** dropdown, select individual scanners or
|
||||||
|
scanner groups to toggle those scanners. The **Scanner** dropdown includes both GitLab scanners, and
|
||||||
|
in GitLab 13.6 and later, custom scanners.
|
||||||
|
|
||||||
Clicking any vulnerability in the table takes you to its
|
Clicking any vulnerability in the table takes you to its
|
||||||
[Vulnerability Details](../vulnerabilities) page to see more information on that vulnerability.
|
[Vulnerability Details](../vulnerabilities) page to see more information on that vulnerability.
|
||||||
To create an issue associated with the vulnerability, click the **Create Issue** button.
|
To create an issue associated with the vulnerability, click the **Create Issue** button.
|
||||||
|
|
|
@ -95,12 +95,13 @@ While you can view and manage the full details of an issue on the [issue page](#
|
||||||
you can also work with multiple issues at a time using the [Issues List](#issues-list),
|
you can also work with multiple issues at a time using the [Issues List](#issues-list),
|
||||||
[Issue Boards](#issue-boards), Issue references, and [Epics](#epics)**(PREMIUM)**.
|
[Issue Boards](#issue-boards), Issue references, and [Epics](#epics)**(PREMIUM)**.
|
||||||
|
|
||||||
Key actions for Issues include:
|
Key actions for issues include:
|
||||||
|
|
||||||
- [Creating issues](managing_issues.md#create-a-new-issue)
|
- [Creating issues](managing_issues.md#create-a-new-issue)
|
||||||
- [Moving issues](managing_issues.md#moving-issues)
|
- [Moving issues](managing_issues.md#moving-issues)
|
||||||
- [Closing issues](managing_issues.md#closing-issues)
|
- [Closing issues](managing_issues.md#closing-issues)
|
||||||
- [Deleting issues](managing_issues.md#deleting-issues)
|
- [Deleting issues](managing_issues.md#deleting-issues)
|
||||||
|
- [Promoting issues](managing_issues.md#promote-an-issue-to-an-epic) **(PREMIUM)**
|
||||||
|
|
||||||
### Issue page
|
### Issue page
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,15 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
# Managing issues
|
# Managing issues
|
||||||
|
|
||||||
[GitLab Issues](index.md) are the fundamental medium for collaborating on ideas and
|
[GitLab Issues](index.md) are the fundamental medium for collaborating on ideas and
|
||||||
planning work in GitLab. [Creating](#create-a-new-issue), [moving](#moving-issues),
|
planning work in GitLab.
|
||||||
[closing](#closing-issues), and [deleting](#deleting-issues) are key actions that
|
|
||||||
you can do with issues.
|
Key actions for issues include:
|
||||||
|
|
||||||
|
- [Creating issues](#create-a-new-issue)
|
||||||
|
- [Moving issues](#moving-issues)
|
||||||
|
- [Closing issues](#closing-issues)
|
||||||
|
- [Deleting issues](#deleting-issues)
|
||||||
|
- [Promoting issues](#promote-an-issue-to-an-epic) **(PREMIUM)**
|
||||||
|
|
||||||
## Create a new issue
|
## Create a new issue
|
||||||
|
|
||||||
|
@ -280,6 +286,23 @@ editing it and clicking on the delete button.
|
||||||
|
|
||||||
![delete issue - button](img/delete_issue.png)
|
![delete issue - button](img/delete_issue.png)
|
||||||
|
|
||||||
|
## Promote an issue to an epic **(PREMIUM)**
|
||||||
|
|
||||||
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3777) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.6.
|
||||||
|
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/37081) to [GitLab Premium](https://about.gitlab.com/pricing/) in 12.8.
|
||||||
|
> - Promoting issues to epics via the UI [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233974) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.6.
|
||||||
|
|
||||||
|
You can promote an issue to an epic in the immediate parent group.
|
||||||
|
|
||||||
|
To promote an issue to an epic:
|
||||||
|
|
||||||
|
1. In an issue, select the vertical ellipsis (**{ellipsis_v}**) button.
|
||||||
|
1. Select **Promote to epic**.
|
||||||
|
|
||||||
|
Alternatively, you can use the `/promote` [quick action](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics).
|
||||||
|
|
||||||
|
Read more about promoting an issue to an epic on the [Manage epics page](../../group/epics/manage_epics.md#promote-an-issue-to-an-epic).
|
||||||
|
|
||||||
## Add an issue to an iteration **(STARTER)**
|
## Add an issue to an iteration **(STARTER)**
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216158) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216158) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2.
|
||||||
|
|
|
@ -528,9 +528,6 @@ msgstr ""
|
||||||
msgid "%{host} sign-in from new location"
|
msgid "%{host} sign-in from new location"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "%{icon}You are about to add %{usersTag} people to the discussion. They will all receive a notification."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
|
msgid "%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -7707,6 +7704,9 @@ msgstr ""
|
||||||
msgid "Could not restore the group"
|
msgid "Could not restore the group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Could not retrieve custom scanners for scanner filter. Please try again later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Could not revoke impersonation token %{token_name}."
|
msgid "Could not revoke impersonation token %{token_name}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -21810,6 +21810,9 @@ msgstr ""
|
||||||
msgid "Promote issue to an epic"
|
msgid "Promote issue to an epic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Promote to epic"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Promote to group label"
|
msgid "Promote to group label"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -25341,6 +25344,9 @@ msgstr ""
|
||||||
msgid "Something went wrong while performing the action."
|
msgid "Something went wrong while performing the action."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Something went wrong while promoting the issue to an epic. Please try again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Something went wrong while reopening a requirement."
|
msgid "Something went wrong while reopening a requirement."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -26956,6 +26962,9 @@ msgstr ""
|
||||||
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
|
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "The issue was successfully promoted to an epic. Redirecting to epic..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "The license for Deploy Board is required to use this feature."
|
msgid "The license for Deploy Board is required to use this feature."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -30764,6 +30773,9 @@ msgstr ""
|
||||||
msgid "You already have pending todo for this alert"
|
msgid "You already have pending todo for this alert"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You are about to add %{usersTag} people to the discussion. They will all receive a notification."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
|
msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["node"],
|
||||||
|
"properties": {
|
||||||
|
"node": {
|
||||||
|
"$ref": "./container_repository.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["id", "name", "path", "location", "createdAt", "updatedAt", "tagsCount", "canDelete"],
|
"required": ["id", "name", "path", "location", "createdAt", "updatedAt", "tagsCount", "canDelete", "expirationPolicyCleanupStatus"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@ -31,6 +31,10 @@
|
||||||
},
|
},
|
||||||
"canDelete": {
|
"canDelete": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"expirationPolicyCleanupStatus": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["UNSCHEDULED", "SCHEDULED", "UNFINISHED", "ONGOING"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,8 @@
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["id", "name", "path", "location", "createdAt", "updatedAt", "tagsCount", "canDelete", "tags"],
|
"required": ["tags"],
|
||||||
|
"allOf": [{ "$ref": "./container_repository.json" }],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"path": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"location": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"expirationPolicyStartedAt": {
|
|
||||||
"type": ["string", "null"]
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": ["string", "null"]
|
|
||||||
},
|
|
||||||
"tagsCount": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"canDelete": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"tags": {
|
"tags": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["nodes"],
|
"required": ["nodes"],
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
import { GlButton, GlDropdown, GlDropdownItem, GlLink, GlModal } from '@gitlab/ui';
|
import { GlButton, GlDropdown, GlDropdownItem, GlLink, GlModal } from '@gitlab/ui';
|
||||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
|
import createFlash, { FLASH_TYPES } from '~/flash';
|
||||||
import { IssuableType } from '~/issuable_show/constants';
|
import { IssuableType } from '~/issuable_show/constants';
|
||||||
import HeaderActions from '~/issue_show/components/header_actions.vue';
|
import HeaderActions from '~/issue_show/components/header_actions.vue';
|
||||||
import { IssuableStatus, IssueStateEvent } from '~/issue_show/constants';
|
import { IssuableStatus, IssueStateEvent } from '~/issue_show/constants';
|
||||||
|
import promoteToEpicMutation from '~/issue_show/queries/promote_to_epic.mutation.graphql';
|
||||||
|
import * as urlUtility from '~/lib/utils/url_utility';
|
||||||
import createStore from '~/notes/stores';
|
import createStore from '~/notes/stores';
|
||||||
|
|
||||||
|
jest.mock('~/flash');
|
||||||
|
|
||||||
describe('HeaderActions component', () => {
|
describe('HeaderActions component', () => {
|
||||||
let dispatchEventSpy;
|
let dispatchEventSpy;
|
||||||
|
let mutateMock;
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let visitUrlSpy;
|
||||||
|
|
||||||
const localVue = createLocalVue();
|
const localVue = createLocalVue();
|
||||||
localVue.use(Vuex);
|
localVue.use(Vuex);
|
||||||
|
@ -16,6 +23,7 @@ describe('HeaderActions component', () => {
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
canCreateIssue: true,
|
canCreateIssue: true,
|
||||||
|
canPromoteToEpic: true,
|
||||||
canReopenIssue: true,
|
canReopenIssue: true,
|
||||||
canReportSpam: true,
|
canReportSpam: true,
|
||||||
canUpdateIssue: true,
|
canUpdateIssue: true,
|
||||||
|
@ -29,7 +37,27 @@ describe('HeaderActions component', () => {
|
||||||
submitAsSpamPath: 'gitlab-org/gitlab-test/-/issues/32/submit_as_spam',
|
submitAsSpamPath: 'gitlab-org/gitlab-test/-/issues/32/submit_as_spam',
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutate = jest.fn().mockResolvedValue({ data: { updateIssue: { errors: [] } } });
|
const updateIssueMutationResponse = { data: { updateIssue: { errors: [] } } };
|
||||||
|
|
||||||
|
const promoteToEpicMutationResponse = {
|
||||||
|
data: {
|
||||||
|
promoteToEpic: {
|
||||||
|
errors: [],
|
||||||
|
epic: {
|
||||||
|
webPath: '/groups/gitlab-org/-/epics/1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const promoteToEpicMutationErrorResponse = {
|
||||||
|
data: {
|
||||||
|
promoteToEpic: {
|
||||||
|
errors: ['The issue has already been promoted to an epic.'],
|
||||||
|
epic: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const findToggleIssueStateButton = () => wrapper.find(GlButton);
|
const findToggleIssueStateButton = () => wrapper.find(GlButton);
|
||||||
|
|
||||||
|
@ -50,7 +78,10 @@ describe('HeaderActions component', () => {
|
||||||
props = {},
|
props = {},
|
||||||
issueState = IssuableStatus.Open,
|
issueState = IssuableStatus.Open,
|
||||||
blockedByIssues = [],
|
blockedByIssues = [],
|
||||||
|
mutateResponse = {},
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
|
mutateMock = jest.fn().mockResolvedValue(mutateResponse);
|
||||||
|
|
||||||
store.getters.getNoteableData.state = issueState;
|
store.getters.getNoteableData.state = issueState;
|
||||||
store.getters.getNoteableData.blocked_by_issues = blockedByIssues;
|
store.getters.getNoteableData.blocked_by_issues = blockedByIssues;
|
||||||
|
|
||||||
|
@ -63,7 +94,7 @@ describe('HeaderActions component', () => {
|
||||||
},
|
},
|
||||||
mocks: {
|
mocks: {
|
||||||
$apollo: {
|
$apollo: {
|
||||||
mutate,
|
mutate: mutateMock,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -73,6 +104,9 @@ describe('HeaderActions component', () => {
|
||||||
if (dispatchEventSpy) {
|
if (dispatchEventSpy) {
|
||||||
dispatchEventSpy.mockRestore();
|
dispatchEventSpy.mockRestore();
|
||||||
}
|
}
|
||||||
|
if (visitUrlSpy) {
|
||||||
|
visitUrlSpy.mockRestore();
|
||||||
|
}
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,7 +124,11 @@ describe('HeaderActions component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
|
dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
|
||||||
|
|
||||||
wrapper = mountComponent({ props: { issueType }, issueState });
|
wrapper = mountComponent({
|
||||||
|
props: { issueType },
|
||||||
|
issueState,
|
||||||
|
mutateResponse: updateIssueMutationResponse,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`has text "${buttonText}"`, () => {
|
it(`has text "${buttonText}"`, () => {
|
||||||
|
@ -100,11 +138,11 @@ describe('HeaderActions component', () => {
|
||||||
it('calls apollo mutation', () => {
|
it('calls apollo mutation', () => {
|
||||||
findToggleIssueStateButton().vm.$emit('click');
|
findToggleIssueStateButton().vm.$emit('click');
|
||||||
|
|
||||||
expect(mutate).toHaveBeenCalledWith(
|
expect(mutateMock).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
iid: defaultProps.iid.toString(),
|
iid: defaultProps.iid,
|
||||||
projectPath: defaultProps.projectPath,
|
projectPath: defaultProps.projectPath,
|
||||||
stateEvent: newIssueState,
|
stateEvent: newIssueState,
|
||||||
},
|
},
|
||||||
|
@ -129,15 +167,17 @@ describe('HeaderActions component', () => {
|
||||||
${'desktop dropdown'} | ${false} | ${findDesktopDropdownItems}
|
${'desktop dropdown'} | ${false} | ${findDesktopDropdownItems}
|
||||||
`('$description', ({ isCloseIssueItemVisible, findDropdownItems }) => {
|
`('$description', ({ isCloseIssueItemVisible, findDropdownItems }) => {
|
||||||
describe.each`
|
describe.each`
|
||||||
description | itemText | isItemVisible | canUpdateIssue | canCreateIssue | isIssueAuthor | canReportSpam
|
description | itemText | isItemVisible | canUpdateIssue | canCreateIssue | isIssueAuthor | canReportSpam | canPromoteToEpic
|
||||||
${`when user can update ${issueType}`} | ${`Close ${issueType}`} | ${isCloseIssueItemVisible} | ${true} | ${true} | ${true} | ${true}
|
${`when user can update ${issueType}`} | ${`Close ${issueType}`} | ${isCloseIssueItemVisible} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||||
${`when user cannot update ${issueType}`} | ${`Close ${issueType}`} | ${false} | ${false} | ${true} | ${true} | ${true}
|
${`when user cannot update ${issueType}`} | ${`Close ${issueType}`} | ${false} | ${false} | ${true} | ${true} | ${true} | ${true}
|
||||||
${`when user can create ${issueType}`} | ${`New ${issueType}`} | ${true} | ${true} | ${true} | ${true} | ${true}
|
${`when user can create ${issueType}`} | ${`New ${issueType}`} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||||
${`when user cannot create ${issueType}`} | ${`New ${issueType}`} | ${false} | ${true} | ${false} | ${true} | ${true}
|
${`when user cannot create ${issueType}`} | ${`New ${issueType}`} | ${false} | ${true} | ${false} | ${true} | ${true} | ${true}
|
||||||
${'when user can report abuse'} | ${'Report abuse'} | ${true} | ${true} | ${true} | ${false} | ${true}
|
${'when user can promote to epic'} | ${'Promote to epic'} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||||
${'when user cannot report abuse'} | ${'Report abuse'} | ${false} | ${true} | ${true} | ${true} | ${true}
|
${'when user cannot promote to epic'} | ${'Promote to epic'} | ${false} | ${true} | ${true} | ${true} | ${true} | ${false}
|
||||||
${'when user can submit as spam'} | ${'Submit as spam'} | ${true} | ${true} | ${true} | ${true} | ${true}
|
${'when user can report abuse'} | ${'Report abuse'} | ${true} | ${true} | ${true} | ${false} | ${true} | ${true}
|
||||||
${'when user cannot submit as spam'} | ${'Submit as spam'} | ${false} | ${true} | ${true} | ${true} | ${false}
|
${'when user cannot report abuse'} | ${'Report abuse'} | ${false} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||||
|
${'when user can submit as spam'} | ${'Submit as spam'} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||||
|
${'when user cannot submit as spam'} | ${'Submit as spam'} | ${false} | ${true} | ${true} | ${true} | ${false} | ${true}
|
||||||
`(
|
`(
|
||||||
'$description',
|
'$description',
|
||||||
({
|
({
|
||||||
|
@ -147,6 +187,7 @@ describe('HeaderActions component', () => {
|
||||||
canCreateIssue,
|
canCreateIssue,
|
||||||
isIssueAuthor,
|
isIssueAuthor,
|
||||||
canReportSpam,
|
canReportSpam,
|
||||||
|
canPromoteToEpic,
|
||||||
}) => {
|
}) => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = mountComponent({
|
wrapper = mountComponent({
|
||||||
|
@ -156,6 +197,7 @@ describe('HeaderActions component', () => {
|
||||||
isIssueAuthor,
|
isIssueAuthor,
|
||||||
issueType,
|
issueType,
|
||||||
canReportSpam,
|
canReportSpam,
|
||||||
|
canPromoteToEpic,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -172,6 +214,65 @@ describe('HeaderActions component', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when "Promote to epic" button is clicked', () => {
|
||||||
|
describe('when response is successful', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
visitUrlSpy = jest.spyOn(urlUtility, 'visitUrl').mockReturnValue({});
|
||||||
|
|
||||||
|
wrapper = mountComponent({
|
||||||
|
mutateResponse: promoteToEpicMutationResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.find('[data-testid="promote-button"]').vm.$emit('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invokes GraphQL mutation when clicked', () => {
|
||||||
|
expect(mutateMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
mutation: promoteToEpicMutation,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
iid: defaultProps.iid,
|
||||||
|
projectPath: defaultProps.projectPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a success message and tells the user they are being redirected', () => {
|
||||||
|
expect(createFlash).toHaveBeenCalledWith({
|
||||||
|
message: 'The issue was successfully promoted to an epic. Redirecting to epic...',
|
||||||
|
type: FLASH_TYPES.SUCCESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('redirects to newly created epic path', () => {
|
||||||
|
expect(visitUrlSpy).toHaveBeenCalledWith(
|
||||||
|
promoteToEpicMutationResponse.data.promoteToEpic.epic.webPath,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when response contains errors', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
visitUrlSpy = jest.spyOn(urlUtility, 'visitUrl').mockReturnValue({});
|
||||||
|
|
||||||
|
wrapper = mountComponent({
|
||||||
|
mutateResponse: promoteToEpicMutationErrorResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.find('[data-testid="promote-button"]').vm.$emit('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows an error message', () => {
|
||||||
|
expect(createFlash).toHaveBeenCalledWith({
|
||||||
|
message: promoteToEpicMutationErrorResponse.data.promoteToEpic.errors.join('; '),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('modal', () => {
|
describe('modal', () => {
|
||||||
const blockedByIssues = [
|
const blockedByIssues = [
|
||||||
{ iid: 13, web_url: 'gitlab-org/gitlab-test/-/issues/13' },
|
{ iid: 13, web_url: 'gitlab-org/gitlab-test/-/issues/13' },
|
||||||
|
@ -197,7 +298,7 @@ describe('HeaderActions component', () => {
|
||||||
it('calls apollo mutation when primary button is clicked', () => {
|
it('calls apollo mutation when primary button is clicked', () => {
|
||||||
findModal().vm.$emit('primary');
|
findModal().vm.$emit('primary');
|
||||||
|
|
||||||
expect(mutate).toHaveBeenCalledWith(
|
expect(mutateMock).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe GitlabSchema.types['ContainerRepositoryCleanupStatus'] do
|
||||||
|
it 'exposes all statuses' do
|
||||||
|
expected_keys = ContainerRepository.expiration_policy_cleanup_statuses
|
||||||
|
.keys
|
||||||
|
.map { |k| k.gsub('cleanup_', '') }
|
||||||
|
.map(&:upcase)
|
||||||
|
expect(described_class.values.keys).to contain_exactly(*expected_keys)
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,7 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe GitlabSchema.types['ContainerRepositoryDetails'] do
|
RSpec.describe GitlabSchema.types['ContainerRepositoryDetails'] do
|
||||||
fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete tags]
|
fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status tags]
|
||||||
|
|
||||||
it { expect(described_class.graphql_name).to eq('ContainerRepositoryDetails') }
|
it { expect(described_class.graphql_name).to eq('ContainerRepositoryDetails') }
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe GitlabSchema.types['ContainerRepository'] do
|
RSpec.describe GitlabSchema.types['ContainerRepository'] do
|
||||||
fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete]
|
fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status]
|
||||||
|
|
||||||
it { expect(described_class.graphql_name).to eq('ContainerRepository') }
|
it { expect(described_class.graphql_name).to eq('ContainerRepository') }
|
||||||
|
|
||||||
|
@ -20,4 +20,12 @@ RSpec.describe GitlabSchema.types['ContainerRepository'] do
|
||||||
is_expected.to have_graphql_type(Types::ContainerRepositoryStatusEnum)
|
is_expected.to have_graphql_type(Types::ContainerRepositoryStatusEnum)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'expiration_policy_cleanup_status field' do
|
||||||
|
subject { described_class.fields['expirationPolicyCleanupStatus'] }
|
||||||
|
|
||||||
|
it 'returns cleanup status enum' do
|
||||||
|
is_expected.to have_graphql_type(Types::ContainerRepositoryCleanupStatusEnum)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,7 +34,7 @@ RSpec.describe 'container repository details' do
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'matches the expected schema' do
|
it 'matches the JSON schema' do
|
||||||
expect(container_repository_details_response).to match_schema('graphql/container_repository_details')
|
expect(container_repository_details_response).to match_schema('graphql/container_repository_details')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,6 +47,10 @@ RSpec.describe 'getting container repositories in a project' do
|
||||||
before do
|
before do
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'matches the JSON schema' do
|
||||||
|
expect(container_repositories_response).to match_schema('graphql/container_repositories')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with different permissions' do
|
context 'with different permissions' do
|
||||||
|
|
Loading…
Reference in New Issue