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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-14 03:14:57 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-14 03:14:57 +0300
commit1d54f384d509d9581730e24a64561e94132e41c1 (patch)
treeb53346e16f2129de0872f9e1cc28bb468fffee04 /app
parentf163fc8ce6d7661ccf0ff9aa4561f6e5a708b71b (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/header_search/components/app.vue1
-rw-r--r--app/assets/javascripts/integrations/constants.js1
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue75
-rw-r--r--app/assets/javascripts/integrations/edit/store/actions.js1
-rw-r--r--app/assets/javascripts/integrations/edit/store/getters.js2
-rw-r--r--app/assets/javascripts/integrations/edit/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/integrations/edit/store/mutations.js3
-rw-r--r--app/assets/javascripts/integrations/edit/store/state.js1
-rw-r--r--app/assets/javascripts/integrations/integration_settings_form.js56
-rw-r--r--app/assets/javascripts/main.js30
-rw-r--r--app/assets/javascripts/snippets/components/edit.vue2
-rw-r--r--app/assets/javascripts/snippets/components/snippet_header.vue2
-rw-r--r--app/assets/stylesheets/framework/contextual_sidebar.scss12
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss57
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss57
-rw-r--r--app/assets/stylesheets/utilities.scss12
-rw-r--r--app/services/issuable_base_service.rb19
-rw-r--r--app/services/issues/set_crm_contacts_service.rb3
-rw-r--r--app/views/groups/_invite_members_side_nav_link.html.haml1
-rw-r--r--app/views/layouts/header/_default.html.haml4
-rw-r--r--app/views/projects/_invite_members_side_nav_link.html.haml1
-rw-r--r--app/views/shared/nav/_scope_menu.html.haml2
-rw-r--r--app/views/shared/nav/_sidebar_hidden_menu_item.html.haml2
-rw-r--r--app/views/shared/nav/_sidebar_menu.html.haml2
-rw-r--r--app/views/shared/nav/_sidebar_menu_item.html.haml2
25 files changed, 227 insertions, 122 deletions
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
index c22f532d7ac..edc6573a489 100644
--- a/app/assets/javascripts/header_search/components/app.vue
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -140,6 +140,7 @@ export default {
class="header-search gl-relative"
>
<gl-search-box-by-type
+ id="search"
v-model="searchText"
role="searchbox"
class="gl-z-index-1"
diff --git a/app/assets/javascripts/integrations/constants.js b/app/assets/javascripts/integrations/constants.js
index 74d99d02fc5..177e3d6f9cc 100644
--- a/app/assets/javascripts/integrations/constants.js
+++ b/app/assets/javascripts/integrations/constants.js
@@ -1,6 +1,5 @@
import { s__, __ } from '~/locale';
-export const TEST_INTEGRATION_EVENT = 'testIntegration';
export const SAVE_INTEGRATION_EVENT = 'saveIntegration';
export const VALIDATE_INTEGRATION_FORM_EVENT = 'validateIntegrationForm';
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index a30c84bd4d2..60a6d13ce69 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -1,14 +1,18 @@
<script>
import { GlButton, GlModalDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import { mapState, mapActions, mapGetters } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
- TEST_INTEGRATION_EVENT,
SAVE_INTEGRATION_EVENT,
+ VALIDATE_INTEGRATION_FORM_EVENT,
+ I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
+ I18N_DEFAULT_ERROR_MESSAGE,
+ I18N_SUCCESSFUL_CONNECTION_MESSAGE,
integrationLevels,
} from '~/integrations/constants';
import eventHub from '../event_hub';
-
+import { testIntegrationSettings } from '../api';
import ActiveCheckbox from './active_checkbox.vue';
import ConfirmationModal from './confirmation_modal.vue';
import DynamicField from './dynamic_field.vue';
@@ -50,18 +54,12 @@ export default {
data() {
return {
integrationActive: false,
+ testingLoading: false,
};
},
computed: {
...mapGetters(['currentKey', 'propsSource', 'isDisabled']),
- ...mapState([
- 'defaultState',
- 'customState',
- 'override',
- 'isSaving',
- 'isTesting',
- 'isResetting',
- ]),
+ ...mapState(['defaultState', 'customState', 'override', 'isSaving', 'isResetting']),
isEditable() {
return this.propsSource.editable;
},
@@ -74,9 +72,18 @@ export default {
this.customState.integrationLevel === integrationLevels.GROUP
);
},
- showReset() {
+ showResetButton() {
return this.isInstanceOrGroupLevel && this.propsSource.resetPath;
},
+ showTestButton() {
+ return this.propsSource.canTest;
+ },
+ disableSaveButton() {
+ return Boolean(this.isResetting || this.testingLoading);
+ },
+ disableResetButton() {
+ return Boolean(this.isSaving || this.testingLoading);
+ },
},
mounted() {
// this form element is defined in Haml
@@ -86,7 +93,6 @@ export default {
...mapActions([
'setOverride',
'setIsSaving',
- 'setIsTesting',
'setIsResetting',
'fetchResetIntegration',
'requestJiraIssueTypes',
@@ -98,17 +104,39 @@ export default {
eventHub.$emit(SAVE_INTEGRATION_EVENT, formValid);
},
onTestClick() {
- this.setIsTesting(true);
+ this.testingLoading = true;
- const formValid = this.form.checkValidity();
- eventHub.$emit(TEST_INTEGRATION_EVENT, formValid);
+ if (!this.form.checkValidity()) {
+ eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
+ return;
+ }
+
+ testIntegrationSettings(this.propsSource.testPath, this.getFormData())
+ .then(({ data: { error, message = I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } }) => {
+ if (error) {
+ eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
+ this.$toast.show(message);
+ return;
+ }
+
+ this.$toast.show(I18N_SUCCESSFUL_CONNECTION_MESSAGE);
+ })
+ .catch((error) => {
+ this.$toast.show(I18N_DEFAULT_ERROR_MESSAGE);
+ Sentry.captureException(error);
+ })
+ .finally(() => {
+ this.testingLoading = false;
+ });
},
onResetClick() {
this.fetchResetIntegration();
},
onRequestJiraIssueTypes() {
- const formData = new FormData(this.form);
- this.requestJiraIssueTypes(formData);
+ this.requestJiraIssueTypes(this.getFormData());
+ },
+ getFormData() {
+ return new FormData(this.form);
},
onToggleIntegrationState(integrationActive) {
this.integrationActive = integrationActive;
@@ -183,7 +211,7 @@ export default {
category="primary"
variant="confirm"
:loading="isSaving"
- :disabled="isDisabled"
+ :disabled="disableSaveButton"
data-qa-selector="save_changes_button"
>
{{ __('Save changes') }}
@@ -196,7 +224,7 @@ export default {
variant="confirm"
type="submit"
:loading="isSaving"
- :disabled="isDisabled"
+ :disabled="disableSaveButton"
data-testid="save-button"
data-qa-selector="save_changes_button"
@click.prevent="onSaveClick"
@@ -205,25 +233,24 @@ export default {
</gl-button>
<gl-button
- v-if="propsSource.canTest"
+ v-if="showTestButton"
category="secondary"
variant="confirm"
- :loading="isTesting"
+ :loading="testingLoading"
:disabled="isDisabled"
- :href="propsSource.testPath"
data-testid="test-button"
@click.prevent="onTestClick"
>
{{ __('Test settings') }}
</gl-button>
- <template v-if="showReset">
+ <template v-if="showResetButton">
<gl-button
v-gl-modal.confirmResetIntegration
category="secondary"
variant="confirm"
:loading="isResetting"
- :disabled="isDisabled"
+ :disabled="disableResetButton"
data-testid="reset-button"
>
{{ __('Reset') }}
diff --git a/app/assets/javascripts/integrations/edit/store/actions.js b/app/assets/javascripts/integrations/edit/store/actions.js
index b81ae1b1cb6..6c1bfaa7858 100644
--- a/app/assets/javascripts/integrations/edit/store/actions.js
+++ b/app/assets/javascripts/integrations/edit/store/actions.js
@@ -11,7 +11,6 @@ import * as types from './mutation_types';
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
export const setIsSaving = ({ commit }, isSaving) => commit(types.SET_IS_SAVING, isSaving);
-export const setIsTesting = ({ commit }, isTesting) => commit(types.SET_IS_TESTING, isTesting);
export const setIsResetting = ({ commit }, isResetting) =>
commit(types.SET_IS_RESETTING, isResetting);
diff --git a/app/assets/javascripts/integrations/edit/store/getters.js b/app/assets/javascripts/integrations/edit/store/getters.js
index 39e14de2d0d..10d370de856 100644
--- a/app/assets/javascripts/integrations/edit/store/getters.js
+++ b/app/assets/javascripts/integrations/edit/store/getters.js
@@ -1,6 +1,6 @@
export const isInheriting = (state) => (state.defaultState === null ? false : !state.override);
-export const isDisabled = (state) => state.isSaving || state.isTesting || state.isResetting;
+export const isDisabled = (state) => state.isSaving || state.isResetting;
export const propsSource = (state, getters) =>
getters.isInheriting ? state.defaultState : state.customState;
diff --git a/app/assets/javascripts/integrations/edit/store/mutation_types.js b/app/assets/javascripts/integrations/edit/store/mutation_types.js
index c681056a515..dd4a79f6fe0 100644
--- a/app/assets/javascripts/integrations/edit/store/mutation_types.js
+++ b/app/assets/javascripts/integrations/edit/store/mutation_types.js
@@ -1,6 +1,5 @@
export const SET_OVERRIDE = 'SET_OVERRIDE';
export const SET_IS_SAVING = 'SET_IS_SAVING';
-export const SET_IS_TESTING = 'SET_IS_TESTING';
export const SET_IS_RESETTING = 'SET_IS_RESETTING';
export const SET_IS_LOADING_JIRA_ISSUE_TYPES = 'SET_IS_LOADING_JIRA_ISSUE_TYPES';
diff --git a/app/assets/javascripts/integrations/edit/store/mutations.js b/app/assets/javascripts/integrations/edit/store/mutations.js
index 279df1b9266..f76081e62f0 100644
--- a/app/assets/javascripts/integrations/edit/store/mutations.js
+++ b/app/assets/javascripts/integrations/edit/store/mutations.js
@@ -7,9 +7,6 @@ export default {
[types.SET_IS_SAVING](state, isSaving) {
state.isSaving = isSaving;
},
- [types.SET_IS_TESTING](state, isTesting) {
- state.isTesting = isTesting;
- },
[types.SET_IS_RESETTING](state, isResetting) {
state.isResetting = isResetting;
},
diff --git a/app/assets/javascripts/integrations/edit/store/state.js b/app/assets/javascripts/integrations/edit/store/state.js
index 1c0b274e4ef..3d40d1b90d5 100644
--- a/app/assets/javascripts/integrations/edit/store/state.js
+++ b/app/assets/javascripts/integrations/edit/store/state.js
@@ -6,7 +6,6 @@ export default ({ defaultState = null, customState = {} } = {}) => {
defaultState,
customState,
isSaving: false,
- isTesting: false,
isResetting: false,
isLoadingJiraIssueTypes: false,
loadingJiraIssueTypesErrorMessage: '',
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index d3c227df1ee..193bc1c4e9b 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -1,15 +1,7 @@
import { delay } from 'lodash';
-import toast from '~/vue_shared/plugins/global_toast';
import initForm from './edit';
import eventHub from './edit/event_hub';
-import {
- TEST_INTEGRATION_EVENT,
- SAVE_INTEGRATION_EVENT,
- VALIDATE_INTEGRATION_FORM_EVENT,
- I18N_DEFAULT_ERROR_MESSAGE,
- I18N_SUCCESSFUL_CONNECTION_MESSAGE,
-} from './constants';
-import { testIntegrationSettings } from './edit/api';
+import { SAVE_INTEGRATION_EVENT, VALIDATE_INTEGRATION_FORM_EVENT } from './constants';
export default class IntegrationSettingsForm {
constructor(formSelector) {
@@ -29,9 +21,6 @@ export default class IntegrationSettingsForm {
document.querySelector('.js-vue-default-integration-settings'),
this.formSelector,
);
- eventHub.$on(TEST_INTEGRATION_EVENT, (formValid) => {
- this.testIntegration(formValid);
- });
eventHub.$on(SAVE_INTEGRATION_EVENT, (formValid) => {
this.saveIntegration(formValid);
});
@@ -53,47 +42,4 @@ export default class IntegrationSettingsForm {
this.vue.$store.dispatch('setIsSaving', false);
}
}
-
- testIntegration(formValid) {
- // Service was marked active so now we check;
- // 1) If form contents are valid
- // 2) If this service can be tested
- // If both conditions are true, we override form submission
- // and test the service using provided configuration.
- if (formValid) {
- this.testSettings(new FormData(this.$form));
- } else {
- eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
- this.vue.$store.dispatch('setIsTesting', false);
- }
- }
-
- /**
- * Get a list of Jira issue types for the currently configured project
- *
- * @param {string} formData - URL encoded string containing the form data
- *
- * @return {Promise}
- */
-
- /**
- * Test Integration config
- */
- testSettings(formData) {
- return testIntegrationSettings(this.testEndPoint, formData)
- .then(({ data }) => {
- if (data.error) {
- toast(`${data.message} ${data.service_response}`);
- } else {
- this.vue.$store.dispatch('receiveJiraIssueTypesSuccess', data.issuetypes);
- toast(I18N_SUCCESSFUL_CONNECTION_MESSAGE);
- }
- })
- .catch(() => {
- toast(I18N_DEFAULT_ERROR_MESSAGE);
- })
- .finally(() => {
- this.vue.$store.dispatch('setIsTesting', false);
- });
- }
}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 0d307d7744b..99e8058b57d 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -14,7 +14,6 @@ import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { initRails } from '~/lib/utils/rails_ujs';
import * as popovers from '~/popovers';
import * as tooltips from '~/tooltips';
-import { initHeaderSearchApp } from '~/header_search';
import initAlertHandler from './alert_handler';
import { addDismissFlashClickListener } from './flash';
import initTodoToggle from './header';
@@ -100,24 +99,29 @@ function deferredInitialisation() {
initFeatureHighlight();
initCopyCodeButton();
- if (gon.features?.newHeaderSearch) {
- initHeaderSearchApp();
- } else {
- const search = document.querySelector('#search');
- if (search) {
- search.addEventListener(
- 'focus',
- () => {
+ const search = document.querySelector('#search');
+ if (search) {
+ search.addEventListener(
+ 'focus',
+ () => {
+ if (gon.features?.newHeaderSearch) {
+ import(/* webpackChunkName: 'globalSearch' */ '~/header_search')
+ .then(async ({ initHeaderSearchApp }) => {
+ await initHeaderSearchApp();
+ document.querySelector('#search').focus();
+ })
+ .catch(() => {});
+ } else {
import(/* webpackChunkName: 'globalSearch' */ './search_autocomplete')
.then(({ default: initSearchAutocomplete }) => {
const searchDropdown = initSearchAutocomplete();
searchDropdown.onSearchInputFocus();
})
.catch(() => {});
- },
- { once: true },
- );
- }
+ }
+ },
+ { once: true },
+ );
}
addSelectOnFocusBehaviour('.js-select-on-focus');
diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue
index f07fb9d926a..e3aa29d5f89 100644
--- a/app/assets/javascripts/snippets/components/edit.vue
+++ b/app/assets/javascripts/snippets/components/edit.vue
@@ -230,7 +230,7 @@ export default {
<gl-button
category="primary"
type="submit"
- variant="success"
+ variant="confirm"
:disabled="updatePrevented"
data-qa-selector="submit_button"
data-testid="snippet-submit-btn"
diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue
index a5c98a7ad90..9b24c8afe37 100644
--- a/app/assets/javascripts/snippets/components/snippet_header.vue
+++ b/app/assets/javascripts/snippets/components/snippet_header.vue
@@ -113,7 +113,7 @@ export default {
href: this.snippet.project
? joinPaths(this.snippet.project.webUrl, '-/snippets/new')
: joinPaths('/', gon.relative_url_root, '/-/snippets/new'),
- variant: 'success',
+ variant: 'confirm',
category: 'secondary',
},
{
diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss
index 03dcbd2b8f9..345c180d164 100644
--- a/app/assets/stylesheets/framework/contextual_sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual_sidebar.scss
@@ -376,6 +376,18 @@
}
}
}
+
+ li > a.gl-link {
+ // undo gl-link text items for things in the sidebar - including sub menus
+ // defined in https://gitlab.com/gitlab-org/gitlab-ui/-/blob/5431e0ca5149d4e02e3d5d617d194ac9609bb82d/src/components/base/link/link.scss
+ @include gl-text-body;
+
+ &:active,
+ &:focus,
+ &:focus:active {
+ @include gl-text-decoration-none;
+ }
+ }
}
.sidebar-sub-level-items {
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index 9914e573247..63305bc592d 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -407,6 +407,34 @@ h1 {
.gl-form-input.form-control::placeholder {
color: #868686;
}
+.gl-icon {
+ fill: currentColor;
+}
+.gl-icon.s12 {
+ width: 12px;
+ height: 12px;
+}
+.gl-icon.s16 {
+ width: 16px;
+ height: 16px;
+}
+.gl-icon.s32 {
+ width: 32px;
+ height: 32px;
+}
+.gl-link {
+ font-size: 0.875rem;
+ color: #428fdc;
+}
+.gl-link:active {
+ color: #9dc7f1;
+}
+.gl-link:active {
+ text-decoration: underline;
+ box-shadow: 0 0 0 1px rgba(51, 51, 51, 0.4),
+ 0 0 0 4px rgba(66, 143, 220, 0.48);
+ outline: none;
+}
.gl-button {
display: inline-flex;
}
@@ -439,6 +467,29 @@ h1 {
outline: none;
background-color: #404040;
}
+.gl-button.gl-button.btn-default:active .gl-icon,
+.gl-button.gl-button.btn-default.active .gl-icon {
+ color: #fafafa;
+}
+.gl-button.gl-button.btn-default .gl-icon {
+ color: #999;
+}
+.gl-search-box-by-type-search-icon {
+ margin: 0.5rem;
+ color: #999;
+ width: 1rem;
+ position: absolute;
+}
+.gl-search-box-by-type {
+ display: flex;
+ position: relative;
+}
+.gl-search-box-by-type-input,
+.gl-search-box-by-type-input.gl-form-input {
+ height: 2rem;
+ padding-right: 2rem;
+ padding-left: 1.75rem;
+}
body,
.form-control,
.search form {
@@ -1334,6 +1385,12 @@ input {
font-weight: 400;
color: #9dc7f1;
}
+.sidebar-top-level-items li > a.gl-link {
+ color: #fafafa;
+}
+.sidebar-top-level-items li > a.gl-link:active {
+ text-decoration: none;
+}
.sidebar-sub-level-items {
padding-top: 0;
padding-bottom: 0;
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index e6d7185e29c..a57202515ad 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -388,6 +388,34 @@ h1 {
.gl-form-input.form-control::placeholder {
color: #868686;
}
+.gl-icon {
+ fill: currentColor;
+}
+.gl-icon.s12 {
+ width: 12px;
+ height: 12px;
+}
+.gl-icon.s16 {
+ width: 16px;
+ height: 16px;
+}
+.gl-icon.s32 {
+ width: 32px;
+ height: 32px;
+}
+.gl-link {
+ font-size: 0.875rem;
+ color: #1f75cb;
+}
+.gl-link:active {
+ color: #0b5cad;
+}
+.gl-link:active {
+ text-decoration: underline;
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.4),
+ 0 0 0 4px rgba(31, 117, 203, 0.48);
+ outline: none;
+}
.gl-button {
display: inline-flex;
}
@@ -420,6 +448,29 @@ h1 {
outline: none;
background-color: #dbdbdb;
}
+.gl-button.gl-button.btn-default:active .gl-icon,
+.gl-button.gl-button.btn-default.active .gl-icon {
+ color: #303030;
+}
+.gl-button.gl-button.btn-default .gl-icon {
+ color: #666;
+}
+.gl-search-box-by-type-search-icon {
+ margin: 0.5rem;
+ color: #666;
+ width: 1rem;
+ position: absolute;
+}
+.gl-search-box-by-type {
+ display: flex;
+ position: relative;
+}
+.gl-search-box-by-type-input,
+.gl-search-box-by-type-input.gl-form-input {
+ height: 2rem;
+ padding-right: 2rem;
+ padding-left: 1.75rem;
+}
body,
.form-control,
.search form {
@@ -1315,6 +1366,12 @@ input {
font-weight: 400;
color: #0b5cad;
}
+.sidebar-top-level-items li > a.gl-link {
+ color: #303030;
+}
+.sidebar-top-level-items li > a.gl-link:active {
+ text-decoration: none;
+}
.sidebar-sub-level-items {
padding-top: 0;
padding-bottom: 0;
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index d2cc1f8640f..a8110d15d8e 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -291,18 +291,6 @@ $gl-line-height-42: px-to-rem(42px);
@include gl-focus($gl-border-size-1, $gray-900, true);
}
-// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2476
-.gl-md-max-w-50p {
- @include gl-media-breakpoint-up(md) {
- max-width: 50%;
- }
-}
-
-// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2497
-.gl-z-index-200 {
- z-index: 200;
-}
-
// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1637
.gl-lg-w-25p {
@include gl-media-breakpoint-up(lg) {
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 2daf098b94a..1d1d9b6bec7 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -56,6 +56,8 @@ class IssuableBaseService < ::BaseProjectService
# confidential attribute is a special type of metadata and needs to be allowed to be set
# by non-members on issues in public projects so that security issues can be reported as confidential.
params.delete(:confidential) unless can?(current_user, :set_confidentiality, issuable)
+ params.delete(:add_contacts) unless can?(current_user, :set_issue_crm_contacts, issuable)
+ params.delete(:remove_contacts) unless can?(current_user, :set_issue_crm_contacts, issuable)
filter_assignees(issuable)
filter_milestone
@@ -206,6 +208,9 @@ class IssuableBaseService < ::BaseProjectService
params[:assignee_ids] = process_assignee_ids(params, extra_assignee_ids: issuable.assignee_ids.to_a)
end
+ params.delete(:remove_contacts)
+ add_crm_contact_emails = params.delete(:add_contacts)
+
issuable.assign_attributes(allowed_create_params(params))
before_create(issuable)
@@ -219,6 +224,7 @@ class IssuableBaseService < ::BaseProjectService
handle_changes(issuable, { params: params })
after_create(issuable)
+ set_crm_contacts(issuable, add_crm_contact_emails)
execute_hooks(issuable)
users_to_invalidate = issuable.allows_reviewers? ? issuable.assignees | issuable.reviewers : issuable.assignees
@@ -229,6 +235,12 @@ class IssuableBaseService < ::BaseProjectService
issuable
end
+ def set_crm_contacts(issuable, add_crm_contact_emails, remove_crm_contact_emails = [])
+ return unless add_crm_contact_emails.present? || remove_crm_contact_emails.present?
+
+ ::Issues::SetCrmContactsService.new(project: project, current_user: current_user, params: { add_emails: add_crm_contact_emails, remove_emails: remove_crm_contact_emails }).execute(issuable)
+ end
+
def before_create(issuable)
# To be overridden by subclasses
end
@@ -254,6 +266,7 @@ class IssuableBaseService < ::BaseProjectService
assign_requested_labels(issuable)
assign_requested_assignees(issuable)
+ assign_requested_crm_contacts(issuable)
if issuable.changed? || params.present?
issuable.assign_attributes(allowed_update_params(params))
@@ -414,6 +427,12 @@ class IssuableBaseService < ::BaseProjectService
issuable.touch
end
+ def assign_requested_crm_contacts(issuable)
+ add_crm_contact_emails = params.delete(:add_contacts)
+ remove_crm_contact_emails = params.delete(:remove_contacts)
+ set_crm_contacts(issuable, add_crm_contact_emails, remove_crm_contact_emails)
+ end
+
def assign_requested_assignees(issuable)
return if issuable.is_a?(Epic)
diff --git a/app/services/issues/set_crm_contacts_service.rb b/app/services/issues/set_crm_contacts_service.rb
index f0d95934ea4..c435ab81b4d 100644
--- a/app/services/issues/set_crm_contacts_service.rb
+++ b/app/services/issues/set_crm_contacts_service.rb
@@ -12,7 +12,7 @@ module Issues
return error_no_permissions unless allowed?
return error_invalid_params unless valid_params?
- @existing_ids = issue.issue_customer_relations_contacts.map(&:contact_id)
+ @existing_ids = issue.customer_relations_contact_ids
determine_changes if params[:replace_ids].present?
return error_too_many if too_many?
@@ -24,6 +24,7 @@ module Issues
if issue.valid?
GraphqlTriggers.issue_crm_contacts_updated(issue)
+ issue.touch
ServiceResponse.success(payload: issue)
else
# The default error isn't very helpful: "Issue customer relations contacts is invalid"
diff --git a/app/views/groups/_invite_members_side_nav_link.html.haml b/app/views/groups/_invite_members_side_nav_link.html.haml
index bccfa9897da..3046669b53b 100644
--- a/app/views/groups/_invite_members_side_nav_link.html.haml
+++ b/app/views/groups/_invite_members_side_nav_link.html.haml
@@ -1,5 +1,4 @@
.js-invite-members-trigger{ data: { trigger_source: 'group-side-nav',
- classes: 'gl-text-decoration-none! gl-shadow-none! gl-text-body!',
icon: 'users',
display_text: title,
trigger_element: 'side-nav'} }
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 01dfecb585a..a7c08bafba6 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -36,7 +36,9 @@
'issues-path' => issues_dashboard_path,
'mr-path' => merge_requests_dashboard_path,
'autocomplete-path' => search_autocomplete_path } }
- %input{ type: "text", placeholder: _('Search or jump to...'), class: 'form-control gl-form-input' }
+ .gl-search-box-by-type
+ = sprite_icon('search', css_class: 'gl-search-box-by-type-search-icon gl-icon')
+ %input{ type: "text", placeholder: _('Search or jump to...'), class: 'form-control gl-form-input gl-search-box-by-type-input', id: 'search', autocomplete: 'off' }
- else
= render 'layouts/search'
%li.nav-item{ class: 'd-none d-sm-inline-block d-lg-none' }
diff --git a/app/views/projects/_invite_members_side_nav_link.html.haml b/app/views/projects/_invite_members_side_nav_link.html.haml
index ea6174d19f0..fae681b1a71 100644
--- a/app/views/projects/_invite_members_side_nav_link.html.haml
+++ b/app/views/projects/_invite_members_side_nav_link.html.haml
@@ -1,5 +1,4 @@
.js-invite-members-trigger{ data: { trigger_source: 'project-side-nav',
- classes: 'gl-text-decoration-none! gl-shadow-none! gl-text-body!',
icon: 'users',
display_text: title,
trigger_element: 'side-nav'} }
diff --git a/app/views/shared/nav/_scope_menu.html.haml b/app/views/shared/nav/_scope_menu.html.haml
index 1a7089fb570..4e570086bf8 100644
--- a/app/views/shared/nav/_scope_menu.html.haml
+++ b/app/views/shared/nav/_scope_menu.html.haml
@@ -1,5 +1,5 @@
= nav_link(**scope_menu.active_routes, html_options: scope_menu.nav_link_html_options) do
- = link_to scope_menu.link, **scope_menu.container_html_options, data: { qa_selector: 'sidebar_menu_link', qa_menu_item: scope_qa_menu_item(scope_menu.container) } do
+ = link_to scope_menu.link, **scope_menu.link_html_options, data: { qa_selector: 'sidebar_menu_link', qa_menu_item: scope_qa_menu_item(scope_menu.container) } do
%span{ class: scope_avatar_classes(scope_menu.container) }
= source_icon(scope_menu.container, alt: scope_menu.title, class: ['avatar', 'avatar-tile', 's32'], width: 32, height: 32)
%span.sidebar-context-title
diff --git a/app/views/shared/nav/_sidebar_hidden_menu_item.html.haml b/app/views/shared/nav/_sidebar_hidden_menu_item.html.haml
index 953f7a8ae60..d0ae5e99707 100644
--- a/app/views/shared/nav/_sidebar_hidden_menu_item.html.haml
+++ b/app/views/shared/nav/_sidebar_hidden_menu_item.html.haml
@@ -1,3 +1,3 @@
%li.hidden
- = link_to sidebar_hidden_menu_item.link, **sidebar_hidden_menu_item.container_html_options do
+ = link_to sidebar_hidden_menu_item.link, **sidebar_hidden_menu_item.link_html_options do
= sidebar_hidden_menu_item.title
diff --git a/app/views/shared/nav/_sidebar_menu.html.haml b/app/views/shared/nav/_sidebar_menu.html.haml
index 3f71368aff3..4c4ceb9ea70 100644
--- a/app/views/shared/nav/_sidebar_menu.html.haml
+++ b/app/views/shared/nav/_sidebar_menu.html.haml
@@ -2,7 +2,7 @@
- if sidebar_menu.menu_with_partial?
= render_if_exists sidebar_menu.menu_partial, **sidebar_menu.menu_partial_options
- else
- = link_to sidebar_menu.link, **sidebar_menu.container_html_options, data: { qa_selector: 'sidebar_menu_link', qa_menu_item: sidebar_menu.title } do
+ = link_to sidebar_menu.link, **sidebar_menu.link_html_options, data: { qa_selector: 'sidebar_menu_link', qa_menu_item: sidebar_menu.title } do
- if sidebar_menu.icon_or_image?
%span.nav-icon-container
- if sidebar_menu.image_path
diff --git a/app/views/shared/nav/_sidebar_menu_item.html.haml b/app/views/shared/nav/_sidebar_menu_item.html.haml
index 674ce593ee2..5452cd486da 100644
--- a/app/views/shared/nav/_sidebar_menu_item.html.haml
+++ b/app/views/shared/nav/_sidebar_menu_item.html.haml
@@ -1,5 +1,5 @@
= nav_link(**sidebar_menu_item.active_routes, html_options: sidebar_menu_item.nav_link_html_options) do
- = link_to sidebar_menu_item.link, **sidebar_menu_item.container_html_options, data: { qa_selector: 'sidebar_menu_item_link', qa_menu_item: sidebar_menu_item.title } do
+ = link_to sidebar_menu_item.link, **sidebar_menu_item.link_html_options, data: { qa_selector: 'sidebar_menu_item_link', qa_menu_item: sidebar_menu_item.title } do
%span
= sidebar_menu_item.title
- if sidebar_menu_item.sprite_icon