Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
22baaecaa8
commit
1c898dc5c1
|
@ -10,7 +10,7 @@ let validEmojiNames = null;
|
|||
export const FALLBACK_EMOJI_KEY = 'grey_question';
|
||||
|
||||
// Keep the version in sync with `lib/gitlab/emoji.rb`
|
||||
export const EMOJI_VERSION = '1';
|
||||
export const EMOJI_VERSION = '2';
|
||||
|
||||
const isLocalStorageAvailable = AccessorUtilities.canUseLocalStorage();
|
||||
|
||||
|
|
|
@ -29,14 +29,20 @@ export default {
|
|||
},
|
||||
},
|
||||
watch: {
|
||||
showLoading(newVal) {
|
||||
if (!newVal) {
|
||||
this.$emit('tree-ready');
|
||||
}
|
||||
showLoading() {
|
||||
this.notifyTreeReady();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.notifyTreeReady();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['toggleTreeOpen']),
|
||||
notifyTreeReady() {
|
||||
if (!this.showLoading) {
|
||||
this.$emit('tree-ready');
|
||||
}
|
||||
},
|
||||
clickedFile() {
|
||||
performanceMarkAndMeasure({ mark: WEBIDE_MARK_FILE_CLICKED });
|
||||
},
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import createFlash from '~/flash';
|
||||
import IdeRouter from '~/ide/ide_router_extension';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { __ } from '~/locale';
|
||||
import {
|
||||
WEBIDE_MARK_FETCH_PROJECT_DATA_START,
|
||||
WEBIDE_MARK_FETCH_PROJECT_DATA_FINISH,
|
||||
|
@ -75,49 +73,34 @@ export const createRouter = (store, defaultBranch) => {
|
|||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.params.namespace && to.params.project) {
|
||||
performanceMarkAndMeasure({ mark: WEBIDE_MARK_FETCH_PROJECT_DATA_START });
|
||||
store
|
||||
.dispatch('getProjectData', {
|
||||
namespace: to.params.namespace,
|
||||
projectId: to.params.project,
|
||||
})
|
||||
.then(() => {
|
||||
const basePath = to.params.pathMatch || '';
|
||||
const projectId = `${to.params.namespace}/${to.params.project}`;
|
||||
const branchId = to.params.branchid;
|
||||
const mergeRequestId = to.params.mrid;
|
||||
const basePath = to.params.pathMatch || '';
|
||||
const projectId = `${to.params.namespace}/${to.params.project}`;
|
||||
const branchId = to.params.branchid;
|
||||
const mergeRequestId = to.params.mrid;
|
||||
|
||||
if (branchId) {
|
||||
performanceMarkAndMeasure({
|
||||
mark: WEBIDE_MARK_FETCH_PROJECT_DATA_FINISH,
|
||||
measures: [
|
||||
{
|
||||
name: WEBIDE_MEASURE_FETCH_PROJECT_DATA,
|
||||
start: WEBIDE_MARK_FETCH_PROJECT_DATA_START,
|
||||
},
|
||||
],
|
||||
});
|
||||
store.dispatch('openBranch', {
|
||||
projectId,
|
||||
branchId,
|
||||
basePath,
|
||||
});
|
||||
} else if (mergeRequestId) {
|
||||
store.dispatch('openMergeRequest', {
|
||||
projectId,
|
||||
mergeRequestId,
|
||||
targetProjectId: to.query.target_project,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
createFlash({
|
||||
message: __('Error while loading the project data. Please try again.'),
|
||||
fadeTransition: false,
|
||||
addBodyClass: true,
|
||||
});
|
||||
throw e;
|
||||
performanceMarkAndMeasure({ mark: WEBIDE_MARK_FETCH_PROJECT_DATA_START });
|
||||
if (branchId) {
|
||||
performanceMarkAndMeasure({
|
||||
mark: WEBIDE_MARK_FETCH_PROJECT_DATA_FINISH,
|
||||
measures: [
|
||||
{
|
||||
name: WEBIDE_MEASURE_FETCH_PROJECT_DATA,
|
||||
start: WEBIDE_MARK_FETCH_PROJECT_DATA_START,
|
||||
},
|
||||
],
|
||||
});
|
||||
store.dispatch('openBranch', {
|
||||
projectId,
|
||||
branchId,
|
||||
basePath,
|
||||
});
|
||||
} else if (mergeRequestId) {
|
||||
store.dispatch('openMergeRequest', {
|
||||
projectId,
|
||||
mergeRequestId,
|
||||
targetProjectId: to.query.target_project,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
|
|
|
@ -34,11 +34,18 @@ Vue.use(PerformancePlugin, {
|
|||
* @param {extendStoreCallback} options.extendStore -
|
||||
* Function that receives the default store and returns an extended one.
|
||||
*/
|
||||
export function initIde(el, options = {}) {
|
||||
export const initIde = (el, options = {}) => {
|
||||
if (!el) return null;
|
||||
|
||||
const { rootComponent = ide, extendStore = identity } = options;
|
||||
|
||||
const store = createStore();
|
||||
const project = JSON.parse(el.dataset.project);
|
||||
store.dispatch('setProject', { project });
|
||||
|
||||
// fire and forget fetching non-critical project info
|
||||
store.dispatch('fetchProjectPermissions');
|
||||
|
||||
const router = createRouter(store, el.dataset.defaultBranch || DEFAULT_BRANCH);
|
||||
|
||||
return new Vue({
|
||||
|
@ -77,7 +84,7 @@ export function initIde(el, options = {}) {
|
|||
return createElement(rootComponent);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start the IDE.
|
||||
|
|
|
@ -1,23 +1,12 @@
|
|||
import getIdeProject from 'ee_else_ce/ide/queries/get_ide_project.query.graphql';
|
||||
import Api from '~/api';
|
||||
import getIdeProject from 'ee_else_ce/ide/queries/get_ide_project.query.graphql';
|
||||
import dismissUserCallout from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
|
||||
import ciConfig from '~/pipeline_editor/graphql/queries/ci_config.query.graphql';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { query, mutate } from './gql';
|
||||
|
||||
const fetchApiProjectData = (projectPath) => Api.project(projectPath).then(({ data }) => data);
|
||||
|
||||
const fetchGqlProjectData = (projectPath) =>
|
||||
query({
|
||||
query: getIdeProject,
|
||||
variables: { projectPath },
|
||||
}).then(({ data }) => ({
|
||||
...data.project,
|
||||
id: getIdFromGraphQLId(data.project.id),
|
||||
}));
|
||||
|
||||
export default {
|
||||
getFileData(endpoint) {
|
||||
return axios.get(endpoint, {
|
||||
|
@ -65,18 +54,6 @@ export default {
|
|||
)
|
||||
.then(({ data }) => data);
|
||||
},
|
||||
getProjectData(namespace, project) {
|
||||
const projectPath = `${namespace}/${project}`;
|
||||
|
||||
return Promise.all([fetchApiProjectData(projectPath), fetchGqlProjectData(projectPath)]).then(
|
||||
([apiProjectData, gqlProjectData]) => ({
|
||||
data: {
|
||||
...apiProjectData,
|
||||
...gqlProjectData,
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
getProjectMergeRequests(projectId, params = {}) {
|
||||
return Api.projectMergeRequests(projectId, params);
|
||||
},
|
||||
|
@ -119,4 +96,13 @@ export default {
|
|||
variables: { input: { featureName: name } },
|
||||
}).then(({ data }) => data);
|
||||
},
|
||||
getProjectPermissionsData(projectPath) {
|
||||
return query({
|
||||
query: getIdeProject,
|
||||
variables: { projectPath },
|
||||
}).then(({ data }) => ({
|
||||
...data.project,
|
||||
id: getIdFromGraphQLId(data.project.id),
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,35 +1,44 @@
|
|||
import { escape } from 'lodash';
|
||||
import createFlash from '~/flash';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { logError } from '~/lib/logger';
|
||||
import api from '../../../api';
|
||||
import service from '../../services';
|
||||
import * as types from '../mutation_types';
|
||||
|
||||
export const getProjectData = ({ commit, state }, { namespace, projectId, force = false } = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!state.projects[`${namespace}/${projectId}`] || force) {
|
||||
commit(types.TOGGLE_LOADING, { entry: state });
|
||||
service
|
||||
.getProjectData(namespace, projectId)
|
||||
.then((res) => res.data)
|
||||
.then((data) => {
|
||||
commit(types.TOGGLE_LOADING, { entry: state });
|
||||
commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
|
||||
commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
|
||||
resolve(data);
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({
|
||||
message: __('Error loading project data. Please try again.'),
|
||||
fadeTransition: false,
|
||||
addBodyClass: true,
|
||||
});
|
||||
reject(new Error(`Project not loaded ${namespace}/${projectId}`));
|
||||
});
|
||||
} else {
|
||||
resolve(state.projects[`${namespace}/${projectId}`]);
|
||||
}
|
||||
const ERROR_LOADING_PROJECT = __('Error loading project data. Please try again.');
|
||||
|
||||
const errorFetchingData = (e) => {
|
||||
logError(ERROR_LOADING_PROJECT, e);
|
||||
|
||||
createFlash({
|
||||
message: ERROR_LOADING_PROJECT,
|
||||
fadeTransition: false,
|
||||
addBodyClass: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const setProject = ({ commit }, { project } = {}) => {
|
||||
if (!project) {
|
||||
return;
|
||||
}
|
||||
const projectPath = project.path_with_namespace;
|
||||
commit(types.SET_PROJECT, { projectPath, project });
|
||||
commit(types.SET_CURRENT_PROJECT, projectPath);
|
||||
};
|
||||
|
||||
export const fetchProjectPermissions = ({ commit, state }) => {
|
||||
const projectPath = state.currentProjectId;
|
||||
if (!projectPath) {
|
||||
return undefined;
|
||||
}
|
||||
return service
|
||||
.getProjectPermissionsData(projectPath)
|
||||
.then((permissions) => {
|
||||
commit(types.UPDATE_PROJECT, { projectPath, props: permissions });
|
||||
})
|
||||
.catch(errorFetchingData);
|
||||
};
|
||||
|
||||
export const refreshLastCommitData = ({ commit }, { projectId, branchId } = {}) =>
|
||||
service
|
||||
|
|
|
@ -8,6 +8,7 @@ export const SET_LINKS = 'SET_LINKS';
|
|||
// Project Mutation Types
|
||||
export const SET_PROJECT = 'SET_PROJECT';
|
||||
export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT';
|
||||
export const UPDATE_PROJECT = 'UPDATE_PROJECT';
|
||||
export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
|
||||
|
||||
// Merge request mutation types
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Vue from 'vue';
|
||||
import * as types from '../mutation_types';
|
||||
|
||||
export default {
|
||||
|
@ -24,4 +25,15 @@ export default {
|
|||
empty_repo: value,
|
||||
});
|
||||
},
|
||||
[types.UPDATE_PROJECT](state, { projectPath, props }) {
|
||||
const project = state.projects[projectPath];
|
||||
|
||||
if (!project || !props) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(props).forEach((key) => {
|
||||
Vue.set(project, key, props[key]);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
<script>
|
||||
import { GlBanner } from '@gitlab/ui';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { s__ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
|
||||
const trackingMixin = Tracking.mixin();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlBanner,
|
||||
},
|
||||
mixins: [trackingMixin],
|
||||
inject: {
|
||||
svgPath: {
|
||||
default: '',
|
||||
},
|
||||
preferencesBehaviorPath: {
|
||||
default: '',
|
||||
},
|
||||
calloutsPath: {
|
||||
default: '',
|
||||
},
|
||||
calloutsFeatureId: {
|
||||
default: '',
|
||||
},
|
||||
trackLabel: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
title: s__('CustomizeHomepageBanner|Do you want to customize this page?'),
|
||||
body: s__(
|
||||
'CustomizeHomepageBanner|This page shows a list of your projects by default but it can be changed to show projects\' activity, groups, your to-do list, assigned issues, assigned merge requests, and more. You can change this under "Homepage content" in your preferences',
|
||||
),
|
||||
button_text: s__('CustomizeHomepageBanner|Go to preferences'),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: true,
|
||||
tracking: {
|
||||
label: this.trackLabel,
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$nextTick(() => {
|
||||
this.addTrackingAttributesToButton();
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.trackOnShow();
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
axios
|
||||
.post(this.calloutsPath, {
|
||||
feature_name: this.calloutsFeatureId,
|
||||
})
|
||||
.catch((e) => {
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings, no-console
|
||||
console.error('Failed to dismiss banner.', e);
|
||||
});
|
||||
|
||||
this.visible = false;
|
||||
this.track('click_dismiss');
|
||||
},
|
||||
trackOnShow() {
|
||||
if (this.visible) this.track('show_home_page_banner');
|
||||
},
|
||||
addTrackingAttributesToButton() {
|
||||
// we can't directly add these on the button like we need to due to
|
||||
// button not being modifiable currently
|
||||
// https://gitlab.com/gitlab-org/gitlab-ui/-/blob/9209ec424e5cca14bc8a1b5c9fa12636d8c83dad/src/components/base/banner/banner.vue#L60
|
||||
const button = this.$refs.banner.$el.querySelector(
|
||||
`[href='${this.preferencesBehaviorPath}']`,
|
||||
);
|
||||
|
||||
if (button) {
|
||||
button.setAttribute('data-track-action', 'click_go_to_preferences');
|
||||
button.setAttribute('data-track-label', this.trackLabel);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-banner
|
||||
v-if="visible"
|
||||
ref="banner"
|
||||
:title="$options.i18n.title"
|
||||
:button-text="$options.i18n.button_text"
|
||||
:button-link="preferencesBehaviorPath"
|
||||
:svg-path="svgPath"
|
||||
@close="handleClose"
|
||||
>
|
||||
<p>
|
||||
{{ $options.i18n.body }}
|
||||
</p>
|
||||
</gl-banner>
|
||||
</template>
|
|
@ -1,5 +1,3 @@
|
|||
import ProjectsList from '~/projects_list';
|
||||
import initCustomizeHomepageBanner from './init_customize_homepage_banner';
|
||||
|
||||
new ProjectsList(); // eslint-disable-line no-new
|
||||
initCustomizeHomepageBanner();
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import CustomizeHomepageBanner from './components/customize_homepage_banner.vue';
|
||||
|
||||
export default () => {
|
||||
const el = document.querySelector('.js-customize-homepage-banner');
|
||||
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
provide: { ...el.dataset },
|
||||
render: (createElement) => createElement(CustomizeHomepageBanner),
|
||||
});
|
||||
};
|
|
@ -29,7 +29,7 @@ module IdeHelper
|
|||
def convert_to_project_entity_json(project)
|
||||
return unless project
|
||||
|
||||
API::Entities::Project.represent(project).to_json
|
||||
API::Entities::Project.represent(project, current_user: current_user).to_json
|
||||
end
|
||||
|
||||
def enable_environments_guidance?
|
||||
|
|
|
@ -6,7 +6,6 @@ module Users
|
|||
GCP_SIGNUP_OFFER = 'gcp_signup_offer'
|
||||
SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed'
|
||||
TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight'
|
||||
CUSTOMIZE_HOMEPAGE = 'customize_homepage'
|
||||
FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version'
|
||||
REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout'
|
||||
UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout'
|
||||
|
@ -36,10 +35,6 @@ module Users
|
|||
!user_dismissed?(SUGGEST_POPOVER_DISMISSED)
|
||||
end
|
||||
|
||||
def show_customize_homepage_banner?
|
||||
current_user.default_dashboard? && !user_dismissed?(CUSTOMIZE_HOMEPAGE)
|
||||
end
|
||||
|
||||
def show_feature_flags_new_version?
|
||||
!user_dismissed?(FEATURE_FLAGS_NEW_VERSION)
|
||||
end
|
||||
|
|
|
@ -844,10 +844,6 @@ class User < ApplicationRecord
|
|||
# Instance methods
|
||||
#
|
||||
|
||||
def default_dashboard?
|
||||
dashboard == self.class.column_defaults['dashboard']
|
||||
end
|
||||
|
||||
def full_path
|
||||
username
|
||||
end
|
||||
|
|
|
@ -24,7 +24,6 @@ module Users
|
|||
buy_pipeline_minutes_notification_dot: 19, # EE-only
|
||||
personal_access_token_expiry: 21, # EE-only
|
||||
suggest_pipeline: 22,
|
||||
customize_homepage: 23,
|
||||
feature_flags_new_version: 24,
|
||||
registration_enabled_callout: 25,
|
||||
new_user_signups_cap_reached: 26, # EE-only
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class DestroyService
|
||||
def initialize(project)
|
||||
@project = project
|
||||
end
|
||||
|
||||
def execute
|
||||
project.events.all.delete_all
|
||||
|
||||
ServiceResponse.success(message: 'Events were deleted.')
|
||||
rescue StandardError
|
||||
ServiceResponse.error(message: 'Failed to remove events.')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project
|
||||
end
|
||||
end
|
|
@ -75,6 +75,18 @@ module Projects
|
|||
response.success?
|
||||
end
|
||||
|
||||
def destroy_events!
|
||||
unless remove_events
|
||||
raise_error(s_('DeleteProject|Failed to remove events. Please try again or contact administrator.'))
|
||||
end
|
||||
end
|
||||
|
||||
def remove_events
|
||||
response = ::Events::DestroyService.new(project).execute
|
||||
|
||||
response.success?
|
||||
end
|
||||
|
||||
def remove_repository(repository)
|
||||
return true unless repository
|
||||
|
||||
|
@ -117,6 +129,7 @@ module Projects
|
|||
log_destroy_event
|
||||
trash_relation_repositories!
|
||||
trash_project_repositories!
|
||||
destroy_events!
|
||||
destroy_web_hooks!
|
||||
destroy_project_bots!
|
||||
|
||||
|
|
|
@ -6,9 +6,17 @@
|
|||
.gl-border-l-solid.gl-border-r-solid.gl-border-gray-100.gl-border-1.gl-p-5
|
||||
%h4
|
||||
= _('Import group from file')
|
||||
%p
|
||||
= s_('GroupsNew|Provide credentials for another instance of GitLab to import your groups directly.')
|
||||
.form-group.gl-display-flex.gl-flex-direction-column
|
||||
.gl-alert.gl-alert-warning{ role: 'alert' }
|
||||
= sprite_icon('warning', css_class: 'gl-icon s16 gl-alert-icon gl-alert-icon-no-title')
|
||||
.gl-alert-body
|
||||
- docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/import/index.md') }
|
||||
- link_end = '</a>'.html_safe
|
||||
= s_('GroupsNew|This feature is deprecated and replaced by %{docs_link_start}Group Migration%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: link_end }
|
||||
- if Feature.enabled?(:bulk_import, default_enabled: :yaml)
|
||||
- enable_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/import/index.md', anchor: 'enable-or-disable-gitlab-group-migration') }
|
||||
= s_('GroupsNew|Ask your administrator to %{enable_link_start}enable%{enable_link_end} Group Migration.').html_safe % { enable_link_start: enable_link_start, enable_link_end: link_end }
|
||||
|
||||
.form-group.gl-display-flex.gl-flex-direction-column.gl-mt-5
|
||||
= f.label :name, _('New group name'), for: 'import_group_name'
|
||||
= f.text_field :name, placeholder: s_('GroupsNew|My Awesome Group'), class: 'js-autofill-group-name gl-form-input col-xs-12 col-sm-8',
|
||||
required: true,
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
.sub-section
|
||||
%h4= s_('GroupSettings|Export group')
|
||||
%p= _('Export this group with all related data.')
|
||||
.gl-alert.gl-alert-warning.gl-mb-4{ role: 'alert' }
|
||||
= sprite_icon('warning', css_class: 'gl-icon s16 gl-alert-icon gl-alert-icon-no-title')
|
||||
.gl-alert-body
|
||||
- docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/import/index.md') }
|
||||
- docs_link_end = '</a>'.html_safe
|
||||
= s_('GroupsNew|This feature is deprecated and replaced by %{docs_link_start}Group Migration%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: docs_link_end }
|
||||
%p
|
||||
- export_information = _('After the export is complete, download the data file from a notification email or from this page. You can then import the data file from the %{strong_text_start}Create new group%{strong_text_end} page of another GitLab instance.') % { strong_text_start: '<strong>'.html_safe, strong_text_end: '</strong>'.html_safe}
|
||||
= export_information.html_safe
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
= render_if_exists "shared/namespace_user_cap_reached_alert"
|
||||
= render_if_exists "shared/new_user_signups_cap_reached_alert"
|
||||
= yield :page_level_alert
|
||||
= yield :customize_homepage_banner
|
||||
- unless @hide_breadcrumbs
|
||||
= render "layouts/nav/breadcrumbs"
|
||||
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
|
||||
|
|
|
@ -1,7 +1,21 @@
|
|||
- @content_class = 'limit-container-width' unless fluid_layout
|
||||
- add_to_breadcrumbs _('Webhook Settings'), namespace_project_hooks_path
|
||||
- add_to_breadcrumbs _('Webhook Settings'), project_hooks_path(@project)
|
||||
- page_title _('Webhook')
|
||||
|
||||
- if @hook.rate_limited?
|
||||
- placeholders = { strong_start: '<strong>'.html_safe,
|
||||
strong_end: '</strong>'.html_safe,
|
||||
limit: @hook.rate_limit,
|
||||
support_link_start: '<a href="https://support.gitlab.com/hc/en-us/requests/new">'.html_safe,
|
||||
support_link_end: '</a>'.html_safe }
|
||||
= render 'shared/global_alert',
|
||||
title: s_('Webhooks|Webhook was automatically disabled'),
|
||||
variant: :danger,
|
||||
is_contained: true,
|
||||
close_button_class: 'js-close' do
|
||||
.gl-alert-body
|
||||
= s_('Webhooks|The webhook was triggered more than %{limit} times per minute and is now disabled. To re-enable this webhook, fix the problems shown in %{strong_start}Recent events%{strong_end}, then re-test your settings. %{support_link_start}Contact Support%{support_link_end} if you need help re-enabling your webhook.').html_safe % placeholders
|
||||
|
||||
.row.gl-mt-3
|
||||
.col-lg-3
|
||||
= render 'shared/web_hooks/title_and_docs', hook: @hook
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
- if show_customize_homepage_banner?
|
||||
= content_for :customize_homepage_banner do
|
||||
.gl-display-none.gl-md-display-block{ class: "gl-pt-6! gl-pb-2! #{(container_class unless @no_container)} #{@content_class}" }
|
||||
.js-customize-homepage-banner{ data: { svg_path: image_path('illustrations/monitoring/getting_started.svg'),
|
||||
preferences_behavior_path: profile_preferences_path(anchor: 'behavior'),
|
||||
callouts_path: callouts_path,
|
||||
callouts_feature_id: Users::CalloutsHelper::CUSTOMIZE_HOMEPAGE,
|
||||
track_label: 'home_page' } }
|
||||
|
||||
= render template: 'dashboard/projects/index'
|
|
@ -1,7 +1,11 @@
|
|||
%li
|
||||
.row
|
||||
.col-md-8.col-lg-7
|
||||
%strong.light-header= hook.url
|
||||
%strong.light-header
|
||||
= hook.url
|
||||
- if hook.rate_limited?
|
||||
%span.gl-badge.badge-danger.badge-pill.sm= _('Disabled')
|
||||
|
||||
%div
|
||||
- hook.class.triggers.each_value do |trigger|
|
||||
- if hook.public_send(trigger)
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347270
|
|||
milestone: '14.6'
|
||||
type: development
|
||||
group: group::access
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -17190,7 +17190,6 @@ Name of the feature that the callout is for.
|
|||
| <a id="usercalloutfeaturenameenumcanary_deployment"></a>`CANARY_DEPLOYMENT` | Callout feature name for canary_deployment. |
|
||||
| <a id="usercalloutfeaturenameenumcloud_licensing_subscription_activation_banner"></a>`CLOUD_LICENSING_SUBSCRIPTION_ACTIVATION_BANNER` | Callout feature name for cloud_licensing_subscription_activation_banner. |
|
||||
| <a id="usercalloutfeaturenameenumcluster_security_warning"></a>`CLUSTER_SECURITY_WARNING` | Callout feature name for cluster_security_warning. |
|
||||
| <a id="usercalloutfeaturenameenumcustomize_homepage"></a>`CUSTOMIZE_HOMEPAGE` | Callout feature name for customize_homepage. |
|
||||
| <a id="usercalloutfeaturenameenumeoa_bronze_plan_banner"></a>`EOA_BRONZE_PLAN_BANNER` | Callout feature name for eoa_bronze_plan_banner. |
|
||||
| <a id="usercalloutfeaturenameenumfeature_flags_new_version"></a>`FEATURE_FLAGS_NEW_VERSION` | Callout feature name for feature_flags_new_version. |
|
||||
| <a id="usercalloutfeaturenameenumgcp_signup_offer"></a>`GCP_SIGNUP_OFFER` | Callout feature name for gcp_signup_offer. |
|
||||
|
|
|
@ -5,9 +5,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
description: 'Writing styles, markup, formatting, and other standards for GitLab Documentation.'
|
||||
---
|
||||
|
||||
# A-Z word list
|
||||
# Recommended word list
|
||||
|
||||
To help ensure consistency in the documentation, follow this guidance.
|
||||
To help ensure consistency in the documentation, the Technical Writing team
|
||||
recommends these wording choices. The GitLab handbook also maintains a list of
|
||||
[top misused terms](https://about.gitlab.com/handbook/communication/top-misused-terms/).
|
||||
|
||||
For guidance not on this page, we defer to these style guides:
|
||||
|
||||
|
@ -769,7 +771,7 @@ Use **you**, **your**, and **yours** instead of [**the user** and **the user's**
|
|||
Documentation should be from the [point of view](https://design.gitlab.com/content/voice-tone#point-of-view) of the reader.
|
||||
|
||||
- Do: You can configure a pipeline.
|
||||
- Do not: Users can configure a pipeline.
|
||||
- Do not: Users can configure a pipeline.
|
||||
|
||||
<!-- vale on -->
|
||||
<!-- markdownlint-enable -->
|
||||
|
|
|
@ -1037,3 +1037,19 @@ scan occurs. Because the cache is downloaded before the analyzer run occurs, the
|
|||
file in the `CI_BUILDS_DIR` directory triggers the dependency scanning job.
|
||||
|
||||
We recommend committing the lock files, which prevents this warning.
|
||||
|
||||
### I no longer get the latest Docker image after setting `DS_MAJOR_VERSION` or `DS_ANALYZER_IMAGE`
|
||||
|
||||
If you have manually set `DS_MAJOR_VERSION` or `DS_ANALYZER_IMAGE` for specific reasons,
|
||||
and now must update your configuration to again get the latest patched versions of our
|
||||
analyzers, edit your `gitlab-ci.yml` file and either:
|
||||
|
||||
- Set your `DS_MAJOR_VERSION` to match the latest version as seen in
|
||||
[our current Dependency Scanning template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml#L18).
|
||||
- If you hardcoded the `DS_ANALYZER_IMAGE` variable directly, change it to match the latest
|
||||
line as found in our [current Dependency Scanning template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml).
|
||||
The line number will vary depending on which scanning job you edited.
|
||||
|
||||
For example, currently the `gemnasium-maven-dependency_scanning` job pulls the latest
|
||||
`gemnasium-maven` Docker image because `DS_ANALYZER_IMAGE` is set to
|
||||
`"$SECURE_ANALYZERS_PREFIX/gemnasium-maven:$DS_MAJOR_VERSION"`.
|
||||
|
|
|
@ -6,7 +6,7 @@ module Gitlab
|
|||
|
||||
# When updating emoji assets increase the version below
|
||||
# and update the version number in `app/assets/javascripts/emoji/index.js`
|
||||
EMOJI_VERSION = 1
|
||||
EMOJI_VERSION = 2
|
||||
|
||||
# Return a Pathname to emoji's current versioned folder
|
||||
#
|
||||
|
|
|
@ -413,7 +413,6 @@ included_attributes:
|
|||
- :b_mode
|
||||
- :too_large
|
||||
- :binary
|
||||
- :diff
|
||||
metrics:
|
||||
- :created_at
|
||||
- :updated_at
|
||||
|
@ -797,6 +796,7 @@ excluded_attributes:
|
|||
- :verification_checksum
|
||||
- :verification_failure
|
||||
merge_request_diff_files:
|
||||
- :diff
|
||||
- :external_diff_offset
|
||||
- :external_diff_size
|
||||
- :merge_request_diff_id
|
||||
|
|
|
@ -131,7 +131,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def setup_diff
|
||||
@relation_hash['diff'] = @relation_hash.delete('utf8_diff')
|
||||
diff = @relation_hash.delete('utf8_diff')
|
||||
|
||||
parsed_relation_hash['diff'] = diff
|
||||
end
|
||||
|
||||
def setup_pipeline
|
||||
|
|
|
@ -10444,15 +10444,6 @@ msgstr ""
|
|||
msgid "Customize your pipeline configuration."
|
||||
msgstr ""
|
||||
|
||||
msgid "CustomizeHomepageBanner|Do you want to customize this page?"
|
||||
msgstr ""
|
||||
|
||||
msgid "CustomizeHomepageBanner|Go to preferences"
|
||||
msgstr ""
|
||||
|
||||
msgid "CustomizeHomepageBanner|This page shows a list of your projects by default but it can be changed to show projects' activity, groups, your to-do list, assigned issues, assigned merge requests, and more. You can change this under \"Homepage content\" in your preferences"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cycle Time"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11419,6 +11410,9 @@ msgstr ""
|
|||
msgid "Delete variable"
|
||||
msgstr ""
|
||||
|
||||
msgid "DeleteProject|Failed to remove events. Please try again or contact administrator."
|
||||
msgstr ""
|
||||
|
||||
msgid "DeleteProject|Failed to remove project repository. Please try again or contact administrator."
|
||||
msgstr ""
|
||||
|
||||
|
@ -13902,9 +13896,6 @@ msgstr ""
|
|||
msgid "Error while loading the merge request. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Error while loading the project data. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Error while migrating %{upload_id}: %{error_message}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -17183,6 +17174,9 @@ msgstr ""
|
|||
msgid "GroupsNew|%{linkStart}Groups%{linkEnd} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|Ask your administrator to %{enable_link_start}enable%{enable_link_end} Group Migration."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|Assemble related projects together and grant members access to several projects at once."
|
||||
msgstr ""
|
||||
|
||||
|
@ -17237,6 +17231,9 @@ msgstr ""
|
|||
msgid "GroupsNew|Provide credentials for another instance of GitLab to import your groups directly."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|This feature is deprecated and replaced by %{docs_link_start}Group Migration%{docs_link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupsNew|To import a group, navigate to the group settings for the GitLab source instance, %{link_start}generate an export file%{link_end}, and upload it here."
|
||||
msgstr ""
|
||||
|
||||
|
@ -28336,9 +28333,6 @@ msgstr ""
|
|||
msgid "Promotions|This feature is locked."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Track activity with Contribution Analytics."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Try it for free"
|
||||
msgstr ""
|
||||
|
||||
|
@ -28351,9 +28345,6 @@ msgstr ""
|
|||
msgid "Promotions|Upgrade your plan to activate Audit Events."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Upgrade your plan to activate Contribution Analytics."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Upgrade your plan to activate Group Webhooks."
|
||||
msgstr ""
|
||||
|
||||
|
@ -28378,9 +28369,6 @@ msgstr ""
|
|||
msgid "Promotions|When you have a lot of issues, it can be hard to get an overview. By adding a weight to your issues, you can get a better idea of the effort, cost, required time, or value of each, and so better manage them."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|With Contribution Analytics you can have an overview for the activity of issues, merge requests, and push events of your organization and its members."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|You can restrict access to protected branches by choosing a role (Maintainers, Developers) as well as certain users."
|
||||
msgstr ""
|
||||
|
||||
|
@ -39427,6 +39415,9 @@ msgstr ""
|
|||
msgid "Webhooks|Tag push events"
|
||||
msgstr ""
|
||||
|
||||
msgid "Webhooks|The webhook was triggered more than %{limit} times per minute and is now disabled. To re-enable this webhook, fix the problems shown in %{strong_start}Recent events%{strong_end}, then re-test your settings. %{support_link_start}Contact Support%{support_link_end} if you need help re-enabling your webhook."
|
||||
msgstr ""
|
||||
|
||||
msgid "Webhooks|Trigger"
|
||||
msgstr ""
|
||||
|
||||
|
@ -39484,6 +39475,9 @@ msgstr ""
|
|||
msgid "Webhooks|Use this token to validate received payloads. It is sent with the request in the X-Gitlab-Token HTTP header."
|
||||
msgstr ""
|
||||
|
||||
msgid "Webhooks|Webhook was automatically disabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Webhooks|Wiki page events"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -131,28 +131,10 @@ RSpec.describe RootController do
|
|||
context 'who uses the default dashboard setting', :aggregate_failures do
|
||||
render_views
|
||||
|
||||
context 'with customize homepage banner' do
|
||||
it 'renders the default dashboard' do
|
||||
get :index
|
||||
it 'renders the default dashboard' do
|
||||
get :index
|
||||
|
||||
expect(response).to render_template 'root/index'
|
||||
expect(response.body).to have_css('.js-customize-homepage-banner')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without customize homepage banner' do
|
||||
before do
|
||||
Users::DismissCalloutService.new(
|
||||
container: nil, current_user: user, params: { feature_name: Users::CalloutsHelper::CUSTOMIZE_HOMEPAGE }
|
||||
).execute
|
||||
end
|
||||
|
||||
it 'renders the default dashboard' do
|
||||
get :index
|
||||
|
||||
expect(response).to render_template 'root/index'
|
||||
expect(response.body).not_to have_css('.js-customize-homepage-banner')
|
||||
end
|
||||
expect(response).to render_template 'dashboard/projects/index'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Root path' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'shows the customize banner', :js do
|
||||
visit root_path
|
||||
|
||||
expect(page).to have_content('Do you want to customize this page?')
|
||||
end
|
||||
end
|
|
@ -38,9 +38,16 @@ describe('IDE tree list', () => {
|
|||
beforeEach(() => {
|
||||
bootstrapWithTree();
|
||||
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
it('emits tree-ready event', () => {
|
||||
expect(vm.$emit).toHaveBeenCalledTimes(1);
|
||||
expect(vm.$emit).toHaveBeenCalledWith('tree-ready');
|
||||
});
|
||||
|
||||
it('renders loading indicator', (done) => {
|
||||
store.state.trees['abcproject/main'].loading = true;
|
||||
|
||||
|
@ -61,9 +68,15 @@ describe('IDE tree list', () => {
|
|||
beforeEach(() => {
|
||||
bootstrapWithTree(emptyBranchTree);
|
||||
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
it('still emits tree-ready event', () => {
|
||||
expect(vm.$emit).toHaveBeenCalledWith('tree-ready');
|
||||
});
|
||||
|
||||
it('does not load files if the branch is empty', () => {
|
||||
expect(vm.$el.textContent).not.toContain('fileName');
|
||||
expect(vm.$el.textContent).toContain('No files');
|
||||
|
|
|
@ -6,6 +6,7 @@ describe('IDE router', () => {
|
|||
const PROJECT_NAMESPACE = 'my-group/sub-group';
|
||||
const PROJECT_NAME = 'my-project';
|
||||
const TEST_PATH = `/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/merge_requests/2`;
|
||||
const DEFAULT_BRANCH = 'default-main';
|
||||
|
||||
let store;
|
||||
let router;
|
||||
|
@ -13,36 +14,48 @@ describe('IDE router', () => {
|
|||
beforeEach(() => {
|
||||
window.history.replaceState({}, '', '/');
|
||||
store = createStore();
|
||||
router = createRouter(store);
|
||||
router = createRouter(store, DEFAULT_BRANCH);
|
||||
jest.spyOn(store, 'dispatch').mockReturnValue(new Promise(() => {}));
|
||||
});
|
||||
|
||||
[
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/main/-/src/blob/`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/main/-/src/blob`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/blob/-/src/blob`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/main/-/src/tree/`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/weird:branch/name-123/-/src/tree/`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/main/-/src/blob`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/main/-/src/edit`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/main/-/src/merge_requests/2`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/blob/-/src/blob`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/edit/blob/-/src/blob`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/merge_requests/2`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/blob`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/edit`,
|
||||
`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}`,
|
||||
].forEach((route) => {
|
||||
it(`finds project path when route is "${route}"`, () => {
|
||||
router.push(route);
|
||||
it.each`
|
||||
route | expectedBranchId | expectedBasePath
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/main/-/src/blob/`} | ${'main'} | ${'src/blob/'}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/main/-/src/blob`} | ${'main'} | ${'src/blob'}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/blob/-/src/blob`} | ${'blob'} | ${'src/blob'}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/main/-/src/tree/`} | ${'main'} | ${'src/tree/'}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/weird:branch/name-123/-/src/tree/`} | ${'weird:branch/name-123'} | ${'src/tree/'}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/main/-/src/blob`} | ${'main'} | ${'src/blob'}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/main/-/src/edit`} | ${'main'} | ${'src/edit'}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/main/-/src/merge_requests/2`} | ${'main'} | ${'src/merge_requests/2'}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/blob/blob/-/src/blob`} | ${'blob'} | ${'src/blob'}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/edit/blob/-/src/blob`} | ${'blob'} | ${'src/blob'}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/tree/blob`} | ${'blob'} | ${''}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/edit`} | ${DEFAULT_BRANCH} | ${''}
|
||||
${`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}`} | ${DEFAULT_BRANCH} | ${''}
|
||||
`('correctly opens Web IDE for $route', ({ route, expectedBranchId, expectedBasePath } = {}) => {
|
||||
router.push(route);
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith('getProjectData', {
|
||||
namespace: PROJECT_NAMESPACE,
|
||||
projectId: PROJECT_NAME,
|
||||
});
|
||||
expect(store.dispatch).toHaveBeenCalledWith('openBranch', {
|
||||
projectId: `${PROJECT_NAMESPACE}/${PROJECT_NAME}`,
|
||||
branchId: expectedBranchId,
|
||||
basePath: expectedBasePath,
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly opens an MR', () => {
|
||||
const expectedId = '2';
|
||||
|
||||
router.push(`/project/${PROJECT_NAMESPACE}/${PROJECT_NAME}/merge_requests/${expectedId}`);
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith('openMergeRequest', {
|
||||
projectId: `${PROJECT_NAMESPACE}/${PROJECT_NAME}`,
|
||||
mergeRequestId: expectedId,
|
||||
targetProjectId: undefined,
|
||||
});
|
||||
expect(store.dispatch).not.toHaveBeenCalledWith('openBranch');
|
||||
});
|
||||
|
||||
it('keeps router in sync when store changes', async () => {
|
||||
expect(router.currentRoute.fullPath).toBe('/');
|
||||
|
||||
|
|
|
@ -216,35 +216,6 @@ describe('IDE services', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('getProjectData', () => {
|
||||
it('combines gql and API requests', () => {
|
||||
const gqlProjectData = {
|
||||
id: 'gid://gitlab/Project/1',
|
||||
userPermissions: {
|
||||
bogus: true,
|
||||
},
|
||||
};
|
||||
const expectedResponse = {
|
||||
...projectData,
|
||||
...gqlProjectData,
|
||||
id: 1,
|
||||
};
|
||||
Api.project.mockReturnValue(Promise.resolve({ data: { ...projectData } }));
|
||||
query.mockReturnValue(Promise.resolve({ data: { project: gqlProjectData } }));
|
||||
|
||||
return services.getProjectData(TEST_NAMESPACE, TEST_PROJECT).then((response) => {
|
||||
expect(response).toEqual({ data: expectedResponse });
|
||||
expect(Api.project).toHaveBeenCalledWith(TEST_PROJECT_ID);
|
||||
expect(query).toHaveBeenCalledWith({
|
||||
query: getIdeProject,
|
||||
variables: {
|
||||
projectPath: TEST_PROJECT_ID,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFiles', () => {
|
||||
let mock;
|
||||
let relativeUrlRoot;
|
||||
|
@ -336,4 +307,38 @@ describe('IDE services', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProjectPermissionsData', () => {
|
||||
const TEST_PROJECT_PATH = 'foo/bar';
|
||||
|
||||
it('queries for the project permissions', () => {
|
||||
const result = { data: { project: projectData } };
|
||||
query.mockResolvedValue(result);
|
||||
|
||||
return services.getProjectPermissionsData(TEST_PROJECT_PATH).then((data) => {
|
||||
expect(data).toEqual(result.data.project);
|
||||
expect(query).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
query: getIdeProject,
|
||||
variables: { projectPath: TEST_PROJECT_PATH },
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('converts the returned GraphQL id to the regular ID number', () => {
|
||||
const projectId = 2;
|
||||
const gqlProjectData = {
|
||||
id: `gid://gitlab/Project/${projectId}`,
|
||||
userPermissions: {
|
||||
bogus: true,
|
||||
},
|
||||
};
|
||||
|
||||
query.mockResolvedValue({ data: { project: gqlProjectData } });
|
||||
return services.getProjectPermissionsData(TEST_PROJECT_PATH).then((data) => {
|
||||
expect(data.id).toBe(projectId);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,9 +2,12 @@ import MockAdapter from 'axios-mock-adapter';
|
|||
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
|
||||
import testAction from 'helpers/vuex_action_helper';
|
||||
import api from '~/api';
|
||||
import createFlash from '~/flash';
|
||||
import service from '~/ide/services';
|
||||
import { createStore } from '~/ide/stores';
|
||||
import {
|
||||
setProject,
|
||||
fetchProjectPermissions,
|
||||
refreshLastCommitData,
|
||||
showBranchNotFoundError,
|
||||
createNewBranchFromDefault,
|
||||
|
@ -13,8 +16,12 @@ import {
|
|||
loadFile,
|
||||
loadBranch,
|
||||
} from '~/ide/stores/actions';
|
||||
import { logError } from '~/lib/logger';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/lib/logger');
|
||||
|
||||
const TEST_PROJECT_ID = 'abc/def';
|
||||
|
||||
describe('IDE store project actions', () => {
|
||||
|
@ -34,6 +41,92 @@ describe('IDE store project actions', () => {
|
|||
mock.restore();
|
||||
});
|
||||
|
||||
describe('setProject', () => {
|
||||
const project = { id: 'foo', path_with_namespace: TEST_PROJECT_ID };
|
||||
const baseMutations = [
|
||||
{
|
||||
type: 'SET_PROJECT',
|
||||
payload: {
|
||||
projectPath: TEST_PROJECT_ID,
|
||||
project,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'SET_CURRENT_PROJECT',
|
||||
payload: TEST_PROJECT_ID,
|
||||
},
|
||||
];
|
||||
|
||||
it.each`
|
||||
desc | payload | expectedMutations
|
||||
${'does not commit any action if project is not passed'} | ${undefined} | ${[]}
|
||||
${'commits correct actions in the correct order by default'} | ${{ project }} | ${[...baseMutations]}
|
||||
`('$desc', async ({ payload, expectedMutations } = {}) => {
|
||||
await testAction({
|
||||
action: setProject,
|
||||
payload,
|
||||
state: store.state,
|
||||
expectedMutations,
|
||||
expectedActions: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchProjectPermissions', () => {
|
||||
const permissionsData = {
|
||||
userPermissions: {
|
||||
bogus: true,
|
||||
},
|
||||
};
|
||||
const permissionsMutations = [
|
||||
{
|
||||
type: 'UPDATE_PROJECT',
|
||||
payload: {
|
||||
projectPath: TEST_PROJECT_ID,
|
||||
props: {
|
||||
...permissionsData,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let spy;
|
||||
|
||||
beforeEach(() => {
|
||||
spy = jest.spyOn(service, 'getProjectPermissionsData');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
createFlash.mockRestore();
|
||||
});
|
||||
|
||||
it.each`
|
||||
desc | projectPath | responseSuccess | expectedMutations
|
||||
${'does not fetch permissions if project does not exist'} | ${undefined} | ${true} | ${[]}
|
||||
${'fetches permission when project is specified'} | ${TEST_PROJECT_ID} | ${true} | ${[...permissionsMutations]}
|
||||
${'flashes an error if the request fails'} | ${TEST_PROJECT_ID} | ${false} | ${[]}
|
||||
`('$desc', async ({ projectPath, expectedMutations, responseSuccess } = {}) => {
|
||||
store.state.currentProjectId = projectPath;
|
||||
if (responseSuccess) {
|
||||
spy.mockResolvedValue(permissionsData);
|
||||
} else {
|
||||
spy.mockRejectedValue();
|
||||
}
|
||||
|
||||
await testAction({
|
||||
action: fetchProjectPermissions,
|
||||
state: store.state,
|
||||
expectedMutations,
|
||||
expectedActions: [],
|
||||
});
|
||||
|
||||
if (!responseSuccess) {
|
||||
expect(logError).toHaveBeenCalled();
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshLastCommitData', () => {
|
||||
beforeEach(() => {
|
||||
store.state.currentProjectId = 'abc/def';
|
||||
|
|
|
@ -3,21 +3,48 @@ import state from '~/ide/stores/state';
|
|||
|
||||
describe('Multi-file store branch mutations', () => {
|
||||
let localState;
|
||||
const nonExistentProj = 'nonexistent';
|
||||
const existingProj = 'abcproject';
|
||||
|
||||
beforeEach(() => {
|
||||
localState = state();
|
||||
localState.projects = { abcproject: { empty_repo: true } };
|
||||
localState.projects = { [existingProj]: { empty_repo: true } };
|
||||
});
|
||||
|
||||
describe('TOGGLE_EMPTY_STATE', () => {
|
||||
it('sets empty_repo for project to passed value', () => {
|
||||
mutations.TOGGLE_EMPTY_STATE(localState, { projectPath: 'abcproject', value: false });
|
||||
mutations.TOGGLE_EMPTY_STATE(localState, { projectPath: existingProj, value: false });
|
||||
|
||||
expect(localState.projects.abcproject.empty_repo).toBe(false);
|
||||
expect(localState.projects[existingProj].empty_repo).toBe(false);
|
||||
|
||||
mutations.TOGGLE_EMPTY_STATE(localState, { projectPath: 'abcproject', value: true });
|
||||
mutations.TOGGLE_EMPTY_STATE(localState, { projectPath: existingProj, value: true });
|
||||
|
||||
expect(localState.projects.abcproject.empty_repo).toBe(true);
|
||||
expect(localState.projects[existingProj].empty_repo).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UPDATE_PROJECT', () => {
|
||||
it.each`
|
||||
desc | projectPath | props | expectedProps
|
||||
${'extends existing project with the passed props'} | ${existingProj} | ${{ foo1: 'bar' }} | ${{ foo1: 'bar' }}
|
||||
${'overrides existing props on the exsiting project'} | ${existingProj} | ${{ empty_repo: false }} | ${{ empty_repo: false }}
|
||||
${'does nothing if the project does not exist'} | ${nonExistentProj} | ${{ foo2: 'bar' }} | ${undefined}
|
||||
${'does nothing if project is not passed'} | ${undefined} | ${{ foo3: 'bar' }} | ${undefined}
|
||||
${'does nothing if the props are not passed'} | ${existingProj} | ${undefined} | ${{}}
|
||||
${'does nothing if the props are empty'} | ${existingProj} | ${{}} | ${{}}
|
||||
`('$desc', ({ projectPath, props, expectedProps } = {}) => {
|
||||
const origProject = localState.projects[projectPath];
|
||||
|
||||
mutations.UPDATE_PROJECT(localState, { projectPath, props });
|
||||
|
||||
if (!expectedProps) {
|
||||
expect(localState.projects[projectPath]).toBeUndefined();
|
||||
} else {
|
||||
expect(localState.projects[projectPath]).toEqual({
|
||||
...origProject,
|
||||
...expectedProps,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
import { GlBanner } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import CustomizeHomepageBanner from '~/pages/dashboard/projects/index/components/customize_homepage_banner.vue';
|
||||
|
||||
const svgPath = '/illustrations/background';
|
||||
const provide = {
|
||||
svgPath,
|
||||
preferencesBehaviorPath: 'some/behavior/path',
|
||||
calloutsPath: 'call/out/path',
|
||||
calloutsFeatureId: 'some-feature-id',
|
||||
trackLabel: 'home_page',
|
||||
};
|
||||
|
||||
const createComponent = () => {
|
||||
return shallowMount(CustomizeHomepageBanner, { provide, stubs: { GlBanner } });
|
||||
};
|
||||
|
||||
describe('CustomizeHomepageBanner', () => {
|
||||
let trackingSpy;
|
||||
let mockAxios;
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
mockAxios = new MockAdapter(axios);
|
||||
document.body.dataset.page = 'some:page';
|
||||
trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
|
||||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
mockAxios.restore();
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
it('should render the banner when not dismissed', () => {
|
||||
expect(wrapper.find(GlBanner).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should close the banner when dismiss is clicked', async () => {
|
||||
mockAxios.onPost(provide.calloutsPath).replyOnce(200);
|
||||
expect(wrapper.find(GlBanner).exists()).toBe(true);
|
||||
wrapper.find(GlBanner).vm.$emit('close');
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find(GlBanner).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('includes the body text from options', () => {
|
||||
expect(wrapper.html()).toContain(wrapper.vm.$options.i18n.body);
|
||||
});
|
||||
|
||||
describe('tracking', () => {
|
||||
const preferencesTrackingEvent = 'click_go_to_preferences';
|
||||
const mockTrackingOnWrapper = () => {
|
||||
unmockTracking();
|
||||
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
};
|
||||
|
||||
it('sets the needed data attributes for tracking button', async () => {
|
||||
await wrapper.vm.$nextTick();
|
||||
const button = wrapper.find(`[href='${wrapper.vm.preferencesBehaviorPath}']`);
|
||||
|
||||
expect(button.attributes('data-track-action')).toEqual(preferencesTrackingEvent);
|
||||
expect(button.attributes('data-track-label')).toEqual(provide.trackLabel);
|
||||
});
|
||||
|
||||
it('sends a tracking event when the banner is shown', () => {
|
||||
const trackCategory = undefined;
|
||||
const trackEvent = 'show_home_page_banner';
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(trackCategory, trackEvent, {
|
||||
label: provide.trackLabel,
|
||||
});
|
||||
});
|
||||
|
||||
it('sends a tracking event when the banner is dismissed', async () => {
|
||||
mockTrackingOnWrapper();
|
||||
mockAxios.onPost(provide.calloutsPath).replyOnce(200);
|
||||
const trackCategory = undefined;
|
||||
const trackEvent = 'click_dismiss';
|
||||
|
||||
wrapper.find(GlBanner).vm.$emit('close');
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(trackingSpy).toHaveBeenCalledWith(trackCategory, trackEvent, {
|
||||
label: provide.trackLabel,
|
||||
});
|
||||
});
|
||||
|
||||
it('sends a tracking event when the button is clicked', async () => {
|
||||
mockTrackingOnWrapper();
|
||||
mockAxios.onPost(provide.calloutsPath).replyOnce(200);
|
||||
const button = wrapper.find(`[href='${wrapper.vm.preferencesBehaviorPath}']`);
|
||||
|
||||
triggerEvent(button.element);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(trackingSpy).toHaveBeenCalledWith('_category_', preferencesTrackingEvent, {
|
||||
label: provide.trackLabel,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -75,7 +75,7 @@ export const mockSuggestedColors = {
|
|||
'#013220': 'Dark green',
|
||||
'#6699cc': 'Blue-gray',
|
||||
'#0000ff': 'Blue',
|
||||
'#e6e6fa': 'Lavendar',
|
||||
'#e6e6fa': 'Lavender',
|
||||
'#9400d3': 'Dark violet',
|
||||
'#330066': 'Deep violet',
|
||||
'#808080': 'Gray',
|
||||
|
|
|
@ -192,6 +192,13 @@ export const commit = async ({ newBranch = false, newMR = false, newBranchName =
|
|||
switchLeftSidebarTab('Commit');
|
||||
screen.getByTestId('begin-commit-button').click();
|
||||
|
||||
await waitForMonacoEditor();
|
||||
|
||||
const mrCheck = await screen.findByLabelText('Start a new merge request');
|
||||
if (Boolean(mrCheck.checked) !== newMR) {
|
||||
mrCheck.click();
|
||||
}
|
||||
|
||||
if (!newBranch) {
|
||||
const option = await screen.findByLabelText(/Commit to .+ branch/);
|
||||
option.click();
|
||||
|
@ -201,12 +208,9 @@ export const commit = async ({ newBranch = false, newMR = false, newBranchName =
|
|||
|
||||
const branchNameInput = await screen.findByTestId('ide-new-branch-name');
|
||||
fireEvent.input(branchNameInput, { target: { value: newBranchName } });
|
||||
|
||||
const mrCheck = await screen.findByLabelText('Start a new merge request');
|
||||
if (Boolean(mrCheck.checked) !== newMR) {
|
||||
mrCheck.click();
|
||||
}
|
||||
}
|
||||
|
||||
screen.getByText('Commit').click();
|
||||
|
||||
await waitForMonacoEditor();
|
||||
};
|
||||
|
|
|
@ -4,16 +4,18 @@ import setWindowLocation from 'helpers/set_window_location_helper';
|
|||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import { initIde } from '~/ide';
|
||||
import extendStore from '~/ide/stores/extend';
|
||||
import { getProject, getEmptyProject } from 'jest/../frontend_integration/test_helpers/fixtures';
|
||||
import { IDE_DATASET } from './mock_data';
|
||||
|
||||
export default (container, { isRepoEmpty = false, path = '', mrId = '' } = {}) => {
|
||||
const projectName = isRepoEmpty ? 'lorem-ipsum-empty' : 'lorem-ipsum';
|
||||
const pathSuffix = mrId ? `merge_requests/${mrId}` : `tree/master/-/${path}`;
|
||||
const project = isRepoEmpty ? getEmptyProject() : getProject();
|
||||
|
||||
setWindowLocation(`${TEST_HOST}/-/ide/project/gitlab-test/${projectName}/${pathSuffix}`);
|
||||
|
||||
const el = document.createElement('div');
|
||||
Object.assign(el.dataset, IDE_DATASET);
|
||||
Object.assign(el.dataset, IDE_DATASET, { project: JSON.stringify(project) });
|
||||
container.appendChild(el);
|
||||
const vm = initIde(el, { extendStore });
|
||||
|
||||
|
|
|
@ -34,10 +34,10 @@ describe('IDE: User opens IDE', () => {
|
|||
expect(await screen.findByText('No files')).toBeDefined();
|
||||
});
|
||||
|
||||
it('shows a "New file" button', async () => {
|
||||
const button = await screen.findByTitle('New file');
|
||||
it('shows a "New file" button', () => {
|
||||
const buttons = screen.queryAllByTitle('New file');
|
||||
|
||||
expect(button.tagName).toEqual('BUTTON');
|
||||
expect(buttons.map((x) => x.tagName)).toContain('BUTTON');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ RSpec.describe IdeHelper do
|
|||
self.instance_variable_set(:@fork_info, fork_info)
|
||||
self.instance_variable_set(:@project, project)
|
||||
|
||||
serialized_project = API::Entities::Project.represent(project).to_json
|
||||
serialized_project = API::Entities::Project.represent(project, current_user: project.creator).to_json
|
||||
|
||||
expect(helper.ide_data)
|
||||
.to include(
|
||||
|
|
|
@ -61,36 +61,6 @@ RSpec.describe Users::CalloutsHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.show_customize_homepage_banner?' do
|
||||
subject { helper.show_customize_homepage_banner? }
|
||||
|
||||
context 'when user has not dismissed' do
|
||||
before do
|
||||
allow(helper).to receive(:user_dismissed?).with(described_class::CUSTOMIZE_HOMEPAGE) { false }
|
||||
end
|
||||
|
||||
context 'when user is on the default dashboard' do
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
|
||||
context 'when user is not on the default dashboard' do
|
||||
before do
|
||||
user.dashboard = 'stars'
|
||||
end
|
||||
|
||||
it { is_expected.to be false }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user dismissed' do
|
||||
before do
|
||||
allow(helper).to receive(:user_dismissed?).with(described_class::CUSTOMIZE_HOMEPAGE) { true }
|
||||
end
|
||||
|
||||
it { is_expected.to be false }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.render_flash_user_callout' do
|
||||
it 'renders the flash_user_callout partial' do
|
||||
expect(helper).to receive(:render)
|
||||
|
|
|
@ -253,7 +253,6 @@ MergeRequestDiffFile:
|
|||
- b_mode
|
||||
- too_large
|
||||
- binary
|
||||
- diff
|
||||
MergeRequestContextCommit:
|
||||
- id
|
||||
- authored_date
|
||||
|
|
|
@ -6138,20 +6138,6 @@ RSpec.describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#default_dashboard?' do
|
||||
it 'is the default dashboard' do
|
||||
user = build(:user)
|
||||
|
||||
expect(user.default_dashboard?).to be true
|
||||
end
|
||||
|
||||
it 'is not the default dashboard' do
|
||||
user = build(:user, dashboard: 'stars')
|
||||
|
||||
expect(user.default_dashboard?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.dormant' do
|
||||
it 'returns dormant users' do
|
||||
freeze_time do
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Events::DestroyService do
|
||||
subject(:service) { described_class.new(project) }
|
||||
|
||||
let_it_be(:project, reload: true) { create(:project, :repository) }
|
||||
let_it_be(:another_project) { create(:project) }
|
||||
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let!(:unrelated_event) { create(:event, :merged, project: another_project, target: another_project, author: user) }
|
||||
|
||||
before do
|
||||
create(:event, :created, project: project, target: project, author: user)
|
||||
create(:event, :created, project: project, target: merge_request, author: user)
|
||||
create(:event, :merged, project: project, target: merge_request, author: user)
|
||||
end
|
||||
|
||||
let(:events) { project.events }
|
||||
|
||||
describe '#execute', :aggregate_failures do
|
||||
it 'deletes the events' do
|
||||
response = nil
|
||||
|
||||
expect { response = subject.execute }.to change(Event, :count).by(-3)
|
||||
|
||||
expect(response).to be_success
|
||||
expect(unrelated_event.reload).to be_present
|
||||
end
|
||||
|
||||
context 'when an error is raised while deleting the records' do
|
||||
before do
|
||||
allow(project).to receive_message_chain(:events, :all, :delete_all).and_raise(ActiveRecord::ActiveRecordError)
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
response = subject.execute
|
||||
|
||||
expect(response).to be_error
|
||||
expect(response.message).to eq 'Failed to remove events.'
|
||||
end
|
||||
|
||||
it 'does not delete events' do
|
||||
expect { subject.execute }.not_to change(Event, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -545,6 +545,27 @@ RSpec.describe Projects::DestroyService, :aggregate_failures do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when project has events' do
|
||||
let!(:event) { create(:event, :created, project: project, target: project, author: user) }
|
||||
|
||||
it 'deletes events from the project' do
|
||||
expect do
|
||||
destroy_project(project, user)
|
||||
end.to change(Event, :count).by(-1)
|
||||
end
|
||||
|
||||
context 'when an error is returned while deleting events' do
|
||||
it 'does not delete project' do
|
||||
allow_next_instance_of(Events::DestroyService) do |instance|
|
||||
allow(instance).to receive(:execute).and_return(ServiceResponse.error(message: 'foo'))
|
||||
end
|
||||
|
||||
expect(destroy_project(project, user)).to be_falsey
|
||||
expect(project.delete_error).to include('Failed to remove events')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'error while destroying', :sidekiq_inline do
|
||||
let!(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
let!(:builds) { create_list(:ci_build, 2, :artifacts, pipeline: pipeline) }
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'projects/hooks/edit' do
|
||||
let(:hook) { create(:project_hook, project: project) }
|
||||
|
||||
let_it_be_with_refind(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
assign :project, project
|
||||
assign :hook, hook
|
||||
end
|
||||
|
||||
it 'renders webhook page with "Recent events"' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_css('h4', text: _('Webhook'))
|
||||
expect(rendered).to have_text(_('Recent events'))
|
||||
end
|
||||
|
||||
context 'webhook is rate limited' do
|
||||
before do
|
||||
allow(hook).to receive(:rate_limited?).and_return(true)
|
||||
end
|
||||
|
||||
it 'renders alert' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_text(s_('Webhooks|Webhook was automatically disabled'))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'projects/hooks/index' do
|
||||
let(:existing_hook) { create(:project_hook, project: project) }
|
||||
let(:new_hook) { ProjectHook.new }
|
||||
|
||||
let_it_be_with_refind(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
assign :project, project
|
||||
assign :hooks, [existing_hook]
|
||||
assign :hook, new_hook
|
||||
end
|
||||
|
||||
it 'renders webhooks page with "Project Hooks"' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_css('h4', text: _('Webhooks'))
|
||||
expect(rendered).to have_text('Project Hooks')
|
||||
expect(rendered).not_to have_css('.gl-badge', text: _('Disabled'))
|
||||
end
|
||||
|
||||
context 'webhook is rate limited' do
|
||||
before do
|
||||
allow(existing_hook).to receive(:rate_limited?).and_return(true)
|
||||
end
|
||||
|
||||
it 'renders "Disabled" badge' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_css('.gl-badge', text: _('Disabled'))
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue