Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8d9b19289a
commit
1f92a1f626
|
@ -3,7 +3,6 @@
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
files: [
|
files: [
|
||||||
'app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue',
|
|
||||||
'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_comment_form.vue',
|
'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_comment_form.vue',
|
||||||
'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_edit_note.vue',
|
'app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_edit_note.vue',
|
||||||
'app/assets/javascripts/admin/statistics_panel/components/app.vue',
|
'app/assets/javascripts/admin/statistics_panel/components/app.vue',
|
||||||
|
|
|
@ -382,6 +382,9 @@ Dangerfile
|
||||||
/ee/app/models/ee/merge_request.rb
|
/ee/app/models/ee/merge_request.rb
|
||||||
/ee/app/services/merge_requests/
|
/ee/app/services/merge_requests/
|
||||||
/ee/app/services/ee/merge_requests
|
/ee/app/services/ee/merge_requests
|
||||||
|
!/ee/app/services/merge_requests/merge_audit_event_service.rb
|
||||||
|
!/ee/app/services/merge_requests/create_from_vulnerability_data_service.rb
|
||||||
|
!/ee/app/services/merge_requests/policy_violations_detected_audit_event_service.rb
|
||||||
/ee/app/workers/merge_requests/
|
/ee/app/workers/merge_requests/
|
||||||
/ee/app/workers/ee/merge_requests
|
/ee/app/workers/ee/merge_requests
|
||||||
/ee/app/workers/merge_request_reset_approvals_worker.rb
|
/ee/app/workers/merge_request_reset_approvals_worker.rb
|
||||||
|
|
|
@ -38,7 +38,6 @@ Gitlab/NoFindInWorkers:
|
||||||
- 'app/workers/issuable_export_csv_worker.rb'
|
- 'app/workers/issuable_export_csv_worker.rb'
|
||||||
- 'app/workers/issues/placement_worker.rb'
|
- 'app/workers/issues/placement_worker.rb'
|
||||||
- 'app/workers/members_destroyer/unassign_issuables_worker.rb'
|
- 'app/workers/members_destroyer/unassign_issuables_worker.rb'
|
||||||
- 'app/workers/merge_requests/handle_assignees_change_worker.rb'
|
|
||||||
- 'app/workers/merge_worker.rb'
|
- 'app/workers/merge_worker.rb'
|
||||||
- 'app/workers/namespaces/root_statistics_worker.rb'
|
- 'app/workers/namespaces/root_statistics_worker.rb'
|
||||||
- 'app/workers/namespaces/schedule_aggregation_worker.rb'
|
- 'app/workers/namespaces/schedule_aggregation_worker.rb'
|
||||||
|
|
|
@ -3702,7 +3702,6 @@ Layout/LineLength:
|
||||||
- 'spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb'
|
- 'spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb'
|
||||||
- 'spec/services/clusters/management/validate_management_project_permissions_service_spec.rb'
|
- 'spec/services/clusters/management/validate_management_project_permissions_service_spec.rb'
|
||||||
- 'spec/services/clusters/update_service_spec.rb'
|
- 'spec/services/clusters/update_service_spec.rb'
|
||||||
- 'spec/services/commits/cherry_pick_service_spec.rb'
|
|
||||||
- 'spec/services/concerns/exclusive_lease_guard_spec.rb'
|
- 'spec/services/concerns/exclusive_lease_guard_spec.rb'
|
||||||
- 'spec/services/concerns/merge_requests/assigns_merge_params_spec.rb'
|
- 'spec/services/concerns/merge_requests/assigns_merge_params_spec.rb'
|
||||||
- 'spec/services/concerns/rate_limited_service_spec.rb'
|
- 'spec/services/concerns/rate_limited_service_spec.rb'
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.23.1
|
0.24.0
|
||||||
|
|
|
@ -365,7 +365,7 @@
|
||||||
{"name":"kubeclient","version":"4.11.0","platform":"ruby","checksum":"4985fcd749fb8c364a668a8350a49821647f03aa52d9ee6cbc582beb8e883fcc"},
|
{"name":"kubeclient","version":"4.11.0","platform":"ruby","checksum":"4985fcd749fb8c364a668a8350a49821647f03aa52d9ee6cbc582beb8e883fcc"},
|
||||||
{"name":"language_server-protocol","version":"3.17.0.3","platform":"ruby","checksum":"3d5c58c02f44a20d972957a9febe386d7e7468ab3900ce6bd2b563dd910c6b3f"},
|
{"name":"language_server-protocol","version":"3.17.0.3","platform":"ruby","checksum":"3d5c58c02f44a20d972957a9febe386d7e7468ab3900ce6bd2b563dd910c6b3f"},
|
||||||
{"name":"launchy","version":"2.5.2","platform":"ruby","checksum":"8aa0441655aec5514008e1d04892c2de3ba57bd337afb984568da091121a241b"},
|
{"name":"launchy","version":"2.5.2","platform":"ruby","checksum":"8aa0441655aec5514008e1d04892c2de3ba57bd337afb984568da091121a241b"},
|
||||||
{"name":"lefthook","version":"1.11.13","platform":"ruby","checksum":"64eee33daf516f27b8948c9734e495d740ba9aa211aadcf34c35cfb1008fbaa2"},
|
{"name":"lefthook","version":"1.11.14","platform":"ruby","checksum":"c11c55f5096f5d38068b66be8a33143899b7095f28a8145c9adf0b3eb611c098"},
|
||||||
{"name":"letter_opener","version":"1.10.0","platform":"ruby","checksum":"2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2"},
|
{"name":"letter_opener","version":"1.10.0","platform":"ruby","checksum":"2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2"},
|
||||||
{"name":"letter_opener_web","version":"3.0.0","platform":"ruby","checksum":"3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860"},
|
{"name":"letter_opener_web","version":"3.0.0","platform":"ruby","checksum":"3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860"},
|
||||||
{"name":"libyajl2","version":"2.1.0","platform":"ruby","checksum":"aa5df6c725776fc050c8418450de0f7c129cb7200b811907c4c0b3b5c0aea0ef"},
|
{"name":"libyajl2","version":"2.1.0","platform":"ruby","checksum":"aa5df6c725776fc050c8418450de0f7c129cb7200b811907c4c0b3b5c0aea0ef"},
|
||||||
|
@ -464,8 +464,8 @@
|
||||||
{"name":"open4","version":"1.3.4","platform":"ruby","checksum":"a1df037310624ecc1ea1d81264b11c83e96d0c3c1c6043108d37d396dcd0f4b1"},
|
{"name":"open4","version":"1.3.4","platform":"ruby","checksum":"a1df037310624ecc1ea1d81264b11c83e96d0c3c1c6043108d37d396dcd0f4b1"},
|
||||||
{"name":"openid_connect","version":"2.3.1","platform":"ruby","checksum":"5d808380cff80d78e3d3d54cfaebe2d6461d835c674faa29e2314a402c1b2182"},
|
{"name":"openid_connect","version":"2.3.1","platform":"ruby","checksum":"5d808380cff80d78e3d3d54cfaebe2d6461d835c674faa29e2314a402c1b2182"},
|
||||||
{"name":"opensearch-ruby","version":"3.4.0","platform":"ruby","checksum":"0a8621686bed3c59b4c23e08cbaef873685a3fe4568e9d2703155ca92b8ca05d"},
|
{"name":"opensearch-ruby","version":"3.4.0","platform":"ruby","checksum":"0a8621686bed3c59b4c23e08cbaef873685a3fe4568e9d2703155ca92b8ca05d"},
|
||||||
{"name":"openssl","version":"3.2.0","platform":"java","checksum":"9a1c870b4175ee90bcd233b5041a5ca8072f5f5f06d404ab3c786aa31daffa02"},
|
{"name":"openssl","version":"3.3.0","platform":"java","checksum":"1755479b8f17a507f0d01020365b4ba96484c033ae88aef410f69d3240261657"},
|
||||||
{"name":"openssl","version":"3.2.0","platform":"ruby","checksum":"3c4bb8760977b4becd2819c6c2569bcf5c6f48b32b9f7a4ce1fd37f996378d14"},
|
{"name":"openssl","version":"3.3.0","platform":"ruby","checksum":"ff3a573fc97ab30f69483fddc80029f91669bf36532859bd182d1836f45aee79"},
|
||||||
{"name":"openssl-signature_algorithm","version":"1.3.0","platform":"ruby","checksum":"a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80"},
|
{"name":"openssl-signature_algorithm","version":"1.3.0","platform":"ruby","checksum":"a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80"},
|
||||||
{"name":"opentelemetry-api","version":"1.2.5","platform":"ruby","checksum":"ab3d9a0566cd2ee068ade40e840bc973383ab8568e693c0c5712f0c789122cc9"},
|
{"name":"opentelemetry-api","version":"1.2.5","platform":"ruby","checksum":"ab3d9a0566cd2ee068ade40e840bc973383ab8568e693c0c5712f0c789122cc9"},
|
||||||
{"name":"opentelemetry-common","version":"0.21.0","platform":"ruby","checksum":"fe891a44583a20bc3217b324aec76d066504494951682d391cfd57d40cd01c98"},
|
{"name":"opentelemetry-common","version":"0.21.0","platform":"ruby","checksum":"fe891a44583a20bc3217b324aec76d066504494951682d391cfd57d40cd01c98"},
|
||||||
|
|
|
@ -1116,7 +1116,7 @@ GEM
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
launchy (2.5.2)
|
launchy (2.5.2)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
lefthook (1.11.13)
|
lefthook (1.11.14)
|
||||||
letter_opener (1.10.0)
|
letter_opener (1.10.0)
|
||||||
launchy (>= 2.2, < 4)
|
launchy (>= 2.2, < 4)
|
||||||
letter_opener_web (3.0.0)
|
letter_opener_web (3.0.0)
|
||||||
|
@ -1324,7 +1324,7 @@ GEM
|
||||||
opensearch-ruby (3.4.0)
|
opensearch-ruby (3.4.0)
|
||||||
faraday (>= 1.0, < 3)
|
faraday (>= 1.0, < 3)
|
||||||
multi_json (>= 1.0)
|
multi_json (>= 1.0)
|
||||||
openssl (3.2.0)
|
openssl (3.3.0)
|
||||||
openssl-signature_algorithm (1.3.0)
|
openssl-signature_algorithm (1.3.0)
|
||||||
openssl (> 2.0)
|
openssl (> 2.0)
|
||||||
opentelemetry-api (1.2.5)
|
opentelemetry-api (1.2.5)
|
||||||
|
|
|
@ -365,7 +365,7 @@
|
||||||
{"name":"kubeclient","version":"4.11.0","platform":"ruby","checksum":"4985fcd749fb8c364a668a8350a49821647f03aa52d9ee6cbc582beb8e883fcc"},
|
{"name":"kubeclient","version":"4.11.0","platform":"ruby","checksum":"4985fcd749fb8c364a668a8350a49821647f03aa52d9ee6cbc582beb8e883fcc"},
|
||||||
{"name":"language_server-protocol","version":"3.17.0.3","platform":"ruby","checksum":"3d5c58c02f44a20d972957a9febe386d7e7468ab3900ce6bd2b563dd910c6b3f"},
|
{"name":"language_server-protocol","version":"3.17.0.3","platform":"ruby","checksum":"3d5c58c02f44a20d972957a9febe386d7e7468ab3900ce6bd2b563dd910c6b3f"},
|
||||||
{"name":"launchy","version":"2.5.2","platform":"ruby","checksum":"8aa0441655aec5514008e1d04892c2de3ba57bd337afb984568da091121a241b"},
|
{"name":"launchy","version":"2.5.2","platform":"ruby","checksum":"8aa0441655aec5514008e1d04892c2de3ba57bd337afb984568da091121a241b"},
|
||||||
{"name":"lefthook","version":"1.11.13","platform":"ruby","checksum":"64eee33daf516f27b8948c9734e495d740ba9aa211aadcf34c35cfb1008fbaa2"},
|
{"name":"lefthook","version":"1.11.14","platform":"ruby","checksum":"c11c55f5096f5d38068b66be8a33143899b7095f28a8145c9adf0b3eb611c098"},
|
||||||
{"name":"letter_opener","version":"1.10.0","platform":"ruby","checksum":"2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2"},
|
{"name":"letter_opener","version":"1.10.0","platform":"ruby","checksum":"2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2"},
|
||||||
{"name":"letter_opener_web","version":"3.0.0","platform":"ruby","checksum":"3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860"},
|
{"name":"letter_opener_web","version":"3.0.0","platform":"ruby","checksum":"3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860"},
|
||||||
{"name":"libyajl2","version":"2.1.0","platform":"ruby","checksum":"aa5df6c725776fc050c8418450de0f7c129cb7200b811907c4c0b3b5c0aea0ef"},
|
{"name":"libyajl2","version":"2.1.0","platform":"ruby","checksum":"aa5df6c725776fc050c8418450de0f7c129cb7200b811907c4c0b3b5c0aea0ef"},
|
||||||
|
@ -464,8 +464,8 @@
|
||||||
{"name":"open4","version":"1.3.4","platform":"ruby","checksum":"a1df037310624ecc1ea1d81264b11c83e96d0c3c1c6043108d37d396dcd0f4b1"},
|
{"name":"open4","version":"1.3.4","platform":"ruby","checksum":"a1df037310624ecc1ea1d81264b11c83e96d0c3c1c6043108d37d396dcd0f4b1"},
|
||||||
{"name":"openid_connect","version":"2.3.1","platform":"ruby","checksum":"5d808380cff80d78e3d3d54cfaebe2d6461d835c674faa29e2314a402c1b2182"},
|
{"name":"openid_connect","version":"2.3.1","platform":"ruby","checksum":"5d808380cff80d78e3d3d54cfaebe2d6461d835c674faa29e2314a402c1b2182"},
|
||||||
{"name":"opensearch-ruby","version":"3.4.0","platform":"ruby","checksum":"0a8621686bed3c59b4c23e08cbaef873685a3fe4568e9d2703155ca92b8ca05d"},
|
{"name":"opensearch-ruby","version":"3.4.0","platform":"ruby","checksum":"0a8621686bed3c59b4c23e08cbaef873685a3fe4568e9d2703155ca92b8ca05d"},
|
||||||
{"name":"openssl","version":"3.2.0","platform":"java","checksum":"9a1c870b4175ee90bcd233b5041a5ca8072f5f5f06d404ab3c786aa31daffa02"},
|
{"name":"openssl","version":"3.3.0","platform":"java","checksum":"1755479b8f17a507f0d01020365b4ba96484c033ae88aef410f69d3240261657"},
|
||||||
{"name":"openssl","version":"3.2.0","platform":"ruby","checksum":"3c4bb8760977b4becd2819c6c2569bcf5c6f48b32b9f7a4ce1fd37f996378d14"},
|
{"name":"openssl","version":"3.3.0","platform":"ruby","checksum":"ff3a573fc97ab30f69483fddc80029f91669bf36532859bd182d1836f45aee79"},
|
||||||
{"name":"openssl-signature_algorithm","version":"1.3.0","platform":"ruby","checksum":"a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80"},
|
{"name":"openssl-signature_algorithm","version":"1.3.0","platform":"ruby","checksum":"a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80"},
|
||||||
{"name":"opentelemetry-api","version":"1.2.5","platform":"ruby","checksum":"ab3d9a0566cd2ee068ade40e840bc973383ab8568e693c0c5712f0c789122cc9"},
|
{"name":"opentelemetry-api","version":"1.2.5","platform":"ruby","checksum":"ab3d9a0566cd2ee068ade40e840bc973383ab8568e693c0c5712f0c789122cc9"},
|
||||||
{"name":"opentelemetry-common","version":"0.21.0","platform":"ruby","checksum":"fe891a44583a20bc3217b324aec76d066504494951682d391cfd57d40cd01c98"},
|
{"name":"opentelemetry-common","version":"0.21.0","platform":"ruby","checksum":"fe891a44583a20bc3217b324aec76d066504494951682d391cfd57d40cd01c98"},
|
||||||
|
|
|
@ -1110,7 +1110,7 @@ GEM
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
launchy (2.5.2)
|
launchy (2.5.2)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
lefthook (1.11.13)
|
lefthook (1.11.14)
|
||||||
letter_opener (1.10.0)
|
letter_opener (1.10.0)
|
||||||
launchy (>= 2.2, < 4)
|
launchy (>= 2.2, < 4)
|
||||||
letter_opener_web (3.0.0)
|
letter_opener_web (3.0.0)
|
||||||
|
@ -1318,7 +1318,7 @@ GEM
|
||||||
opensearch-ruby (3.4.0)
|
opensearch-ruby (3.4.0)
|
||||||
faraday (>= 1.0, < 3)
|
faraday (>= 1.0, < 3)
|
||||||
multi_json (>= 1.0)
|
multi_json (>= 1.0)
|
||||||
openssl (3.2.0)
|
openssl (3.3.0)
|
||||||
openssl-signature_algorithm (1.3.0)
|
openssl-signature_algorithm (1.3.0)
|
||||||
openssl (> 2.0)
|
openssl (> 2.0)
|
||||||
opentelemetry-api (1.2.5)
|
opentelemetry-api (1.2.5)
|
||||||
|
|
|
@ -105,6 +105,7 @@ export default {
|
||||||
'contextCommits',
|
'contextCommits',
|
||||||
'contextCommitsLoadingError',
|
'contextCommitsLoadingError',
|
||||||
'selectedCommits',
|
'selectedCommits',
|
||||||
|
// eslint-disable-next-line vue/no-unused-properties -- searchText is mapped from Vuex and used in handleSearchCommits(),
|
||||||
'searchText',
|
'searchText',
|
||||||
'toRemoveCommits',
|
'toRemoveCommits',
|
||||||
]),
|
]),
|
||||||
|
@ -281,9 +282,6 @@ export default {
|
||||||
handleModalHide() {
|
handleModalHide() {
|
||||||
this.resetModalState();
|
this.resetModalState();
|
||||||
},
|
},
|
||||||
shouldShowInputDateFormat(value) {
|
|
||||||
return ['Committed-before', 'Committed-after'].indexOf(value) !== -1;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlSprintf, GlIcon, GlButton } from '@gitlab/ui';
|
import { GlSprintf, GlIcon, GlButton } from '@gitlab/ui';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
import { mapState } from 'pinia';
|
import { mapState } from 'pinia';
|
||||||
import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
|
import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
|
||||||
import { sprintf, __ } from '~/locale';
|
import { sprintf, __ } from '~/locale';
|
||||||
|
@ -11,6 +9,7 @@ import {
|
||||||
getLineClasses,
|
getLineClasses,
|
||||||
} from '~/notes/components/multiline_comment_utils';
|
} from '~/notes/components/multiline_comment_utils';
|
||||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
import resolvedStatusMixin from '../mixins/resolved_status';
|
import resolvedStatusMixin from '../mixins/resolved_status';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -28,7 +27,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useLegacyDiffs, ['getDiffFileByHash']),
|
...mapState(useLegacyDiffs, ['getDiffFileByHash']),
|
||||||
...mapGetters(['getDiscussion']),
|
...mapState(useNotes, ['getDiscussion']),
|
||||||
iconName() {
|
iconName() {
|
||||||
return this.isDiffDiscussion || this.draft.line_code ? 'doc-text' : 'comment';
|
return this.isDiffDiscussion || this.draft.line_code ? 'doc-text' : 'comment';
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { mapState } from 'pinia';
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
import { sprintf, s__, __ } from '~/locale';
|
import { sprintf, s__, __ } from '~/locale';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -21,7 +21,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['isDiscussionResolved']),
|
...mapState(useNotes, ['isDiscussionResolved']),
|
||||||
resolvedStatusMessage() {
|
resolvedStatusMessage() {
|
||||||
let message;
|
let message;
|
||||||
const discussionResolved = this.isDiscussionResolved(
|
const discussionResolved = this.isDiscussionResolved(
|
||||||
|
|
|
@ -26,14 +26,18 @@ export const TAB_NAMES = Object.freeze({
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AlertDetailsTable,
|
|
||||||
DescriptionComponent,
|
DescriptionComponent,
|
||||||
GlTab,
|
GlTab,
|
||||||
GlTabs,
|
GlTabs,
|
||||||
HighlightBar,
|
HighlightBar,
|
||||||
...(gon.features?.hideIncidentManagementFeatures ? {} : { TimelineTab }),
|
...(gon.features?.hideIncidentManagementFeatures
|
||||||
IncidentMetricTab: () =>
|
? {}
|
||||||
import('ee_component/issues/show/components/incidents/incident_metric_tab.vue'),
|
: {
|
||||||
|
AlertDetailsTable,
|
||||||
|
TimelineTab,
|
||||||
|
IncidentMetricTab: () =>
|
||||||
|
import('ee_component/issues/show/components/incidents/incident_metric_tab.vue'),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
inject: ['fullPath', 'iid', 'hasLinkedAlerts', 'uploadMetricsFeatureAvailable'],
|
inject: ['fullPath', 'iid', 'hasLinkedAlerts', 'uploadMetricsFeatureAvailable'],
|
||||||
i18n: incidentTabsI18n,
|
i18n: incidentTabsI18n,
|
||||||
|
@ -75,10 +79,10 @@ export default {
|
||||||
tabMapping() {
|
tabMapping() {
|
||||||
const availableTabs = [TAB_NAMES.SUMMARY];
|
const availableTabs = [TAB_NAMES.SUMMARY];
|
||||||
|
|
||||||
if (this.uploadMetricsFeatureAvailable) {
|
if (this.uploadMetricsFeatureAvailable && this.showIncidentManagementFeatures) {
|
||||||
availableTabs.push(TAB_NAMES.METRICS);
|
availableTabs.push(TAB_NAMES.METRICS);
|
||||||
}
|
}
|
||||||
if (this.hasLinkedAlerts) {
|
if (this.hasLinkedAlerts && this.showIncidentManagementFeatures) {
|
||||||
availableTabs.push(TAB_NAMES.ALERTS);
|
availableTabs.push(TAB_NAMES.ALERTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,18 +162,20 @@ export default {
|
||||||
<description-component v-bind="$attrs" v-on="$listeners" />
|
<description-component v-bind="$attrs" v-on="$listeners" />
|
||||||
</gl-tab>
|
</gl-tab>
|
||||||
<gl-tab
|
<gl-tab
|
||||||
v-if="uploadMetricsFeatureAvailable"
|
v-if="uploadMetricsFeatureAvailable && showIncidentManagementFeatures"
|
||||||
:title="$options.i18n.metricsTitle"
|
:title="$options.i18n.metricsTitle"
|
||||||
data-testid="metrics-tab"
|
data-testid="metrics-tab"
|
||||||
>
|
>
|
||||||
|
<!-- eslint-disable-next-line vue/no-undef-components -->
|
||||||
<incident-metric-tab />
|
<incident-metric-tab />
|
||||||
</gl-tab>
|
</gl-tab>
|
||||||
<gl-tab
|
<gl-tab
|
||||||
v-if="hasLinkedAlerts"
|
v-if="hasLinkedAlerts && showIncidentManagementFeatures"
|
||||||
class="alert-management-details"
|
class="alert-management-details"
|
||||||
:title="$options.i18n.alertsTitle"
|
:title="$options.i18n.alertsTitle"
|
||||||
data-testid="alert-details-tab"
|
data-testid="alert-details-tab"
|
||||||
>
|
>
|
||||||
|
<!-- eslint-disable-next-line vue/no-undef-components -->
|
||||||
<alert-details-table :alert="alert" :loading="loading" />
|
<alert-details-table :alert="alert" :loading="loading" />
|
||||||
</gl-tab>
|
</gl-tab>
|
||||||
<gl-tab
|
<gl-tab
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlAvatar, GlAvatarLink } from '@gitlab/ui';
|
import { GlAvatar, GlAvatarLink } from '@gitlab/ui';
|
||||||
import { escape } from 'lodash';
|
import { escape } from 'lodash';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { mapActions } from 'pinia';
|
||||||
import { mapActions } from 'vuex';
|
|
||||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||||
import { truncateSha } from '~/lib/utils/text_utility';
|
import { truncateSha } from '~/lib/utils/text_utility';
|
||||||
import { s__, __, sprintf } from '~/locale';
|
import { s__, __, sprintf } from '~/locale';
|
||||||
import { FILE_DIFF_POSITION_TYPE } from '~/diffs/constants';
|
import { FILE_DIFF_POSITION_TYPE } from '~/diffs/constants';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
import NoteEditedText from './note_edited_text.vue';
|
import NoteEditedText from './note_edited_text.vue';
|
||||||
import NoteHeader from './note_header.vue';
|
import NoteHeader from './note_header.vue';
|
||||||
import ToggleRepliesWidget from './toggle_replies_widget.vue';
|
import ToggleRepliesWidget from './toggle_replies_widget.vue';
|
||||||
|
@ -102,7 +102,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['toggleDiscussion']),
|
...mapActions(useNotes, ['toggleDiscussion']),
|
||||||
toggleDiscussionHandler() {
|
toggleDiscussionHandler() {
|
||||||
this.toggleDiscussion({ discussionId: this.discussion.id });
|
this.toggleDiscussion({ discussionId: this.discussion.id });
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
|
import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { mapState, mapActions } from 'pinia';
|
||||||
import { mapActions } from 'vuex';
|
|
||||||
import { mapState } from 'pinia';
|
|
||||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||||
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
|
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
|
||||||
|
@ -13,6 +11,7 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
|
||||||
import { isCollapsed } from '~/diffs/utils/diff_file';
|
import { isCollapsed } from '~/diffs/utils/diff_file';
|
||||||
import { FILE_DIFF_POSITION_TYPE, IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
|
import { FILE_DIFF_POSITION_TYPE, IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
|
||||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
|
||||||
const FIRST_CHAR_REGEX = /^(\+|-| )/;
|
const FIRST_CHAR_REGEX = /^(\+|-| )/;
|
||||||
|
|
||||||
|
@ -117,7 +116,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['fetchDiscussionDiffLines']),
|
...mapActions(useNotes, ['fetchDiscussionDiffLines']),
|
||||||
fetchDiff() {
|
fetchDiff() {
|
||||||
this.error = false;
|
this.error = false;
|
||||||
this.fetchDiscussionDiffLines(this.discussion)
|
this.fetchDiscussionDiffLines(this.discussion)
|
||||||
|
|
|
@ -7,8 +7,7 @@ import {
|
||||||
GlButtonGroup,
|
GlButtonGroup,
|
||||||
GlTooltipDirective,
|
GlTooltipDirective,
|
||||||
} from '@gitlab/ui';
|
} from '@gitlab/ui';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { mapActions, mapState } from 'pinia';
|
||||||
import { mapActions, mapState } from 'vuex';
|
|
||||||
import { InternalEvents } from '~/tracking';
|
import { InternalEvents } from '~/tracking';
|
||||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
|
@ -19,6 +18,7 @@ import {
|
||||||
MR_FILTER_TRACKING_USER_COMMENTS,
|
MR_FILTER_TRACKING_USER_COMMENTS,
|
||||||
MR_FILTER_TRACKING_BOT_COMMENTS,
|
MR_FILTER_TRACKING_BOT_COMMENTS,
|
||||||
} from '~/notes/constants';
|
} from '~/notes/constants';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
|
||||||
const filterOptionToTrackingEventMap = {
|
const filterOptionToTrackingEventMap = {
|
||||||
comments: MR_FILTER_TRACKING_USER_COMMENTS,
|
comments: MR_FILTER_TRACKING_USER_COMMENTS,
|
||||||
|
@ -46,10 +46,7 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState(useNotes, ['mergeRequestFilters', 'discussionSortOrder']),
|
||||||
mergeRequestFilters: (state) => state.notes.mergeRequestFilters,
|
|
||||||
discussionSortOrder: (state) => state.notes.discussionSortOrder,
|
|
||||||
}),
|
|
||||||
selectedFilterText() {
|
selectedFilterText() {
|
||||||
const { length } = this.mergeRequestFilters;
|
const { length } = this.mergeRequestFilters;
|
||||||
|
|
||||||
|
@ -80,7 +77,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['updateMergeRequestFilters', 'setDiscussionSortDirection']),
|
...mapActions(useNotes, ['updateMergeRequestFilters', 'setDiscussionSortDirection']),
|
||||||
updateSortDirection() {
|
updateSortDirection() {
|
||||||
this.setDiscussionSortDirection({
|
this.setDiscussionSortDirection({
|
||||||
direction: this.isSortAsc ? 'desc' : 'asc',
|
direction: this.isSortAsc ? 'desc' : 'asc',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlFormSelect, GlSprintf } from '@gitlab/ui';
|
import { GlFormSelect, GlSprintf } from '@gitlab/ui';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { mapActions } from 'pinia';
|
||||||
import { mapActions } from 'vuex';
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
import { getSymbol, getLineClasses } from './multiline_comment_utils';
|
import { getSymbol, getLineClasses } from './multiline_comment_utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -48,7 +48,7 @@ export default {
|
||||||
this.setSelectedCommentPosition();
|
this.setSelectedCommentPosition();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['setSelectedCommentPosition']),
|
...mapActions(useNotes, ['setSelectedCommentPosition']),
|
||||||
getSymbol({ type }) {
|
getSymbol({ type }) {
|
||||||
return getSymbol(type);
|
return getSymbol(type);
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,8 +6,7 @@ import {
|
||||||
GlDisclosureDropdownItem,
|
GlDisclosureDropdownItem,
|
||||||
GlDisclosureDropdownGroup,
|
GlDisclosureDropdownGroup,
|
||||||
} from '@gitlab/ui';
|
} from '@gitlab/ui';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { mapActions, mapState } from 'pinia';
|
||||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
|
||||||
import Api from '~/api';
|
import Api from '~/api';
|
||||||
import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
|
import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
|
||||||
import { createAlert } from '~/alert';
|
import { createAlert } from '~/alert';
|
||||||
|
@ -16,6 +15,7 @@ import { __, sprintf } from '~/locale';
|
||||||
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
|
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
|
||||||
import { splitCamelCase } from '~/lib/utils/text_utility';
|
import { splitCamelCase } from '~/lib/utils/text_utility';
|
||||||
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
|
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
import ReplyButton from './note_actions/reply_button.vue';
|
import ReplyButton from './note_actions/reply_button.vue';
|
||||||
import TimelineEventButton from './note_actions/timeline_event_button.vue';
|
import TimelineEventButton from './note_actions/timeline_event_button.vue';
|
||||||
|
|
||||||
|
@ -146,8 +146,12 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['isPromoteCommentToTimelineEventInProgress']),
|
...mapState(useNotes, [
|
||||||
...mapGetters(['getUserDataByProp', 'getNoteableData', 'canUserAddIncidentTimelineEvents']),
|
'isPromoteCommentToTimelineEventInProgress',
|
||||||
|
'getUserDataByProp',
|
||||||
|
'getNoteableData',
|
||||||
|
'canUserAddIncidentTimelineEvents',
|
||||||
|
]),
|
||||||
shouldShowActionsDropdown() {
|
shouldShowActionsDropdown() {
|
||||||
return this.currentUserId;
|
return this.currentUserId;
|
||||||
},
|
},
|
||||||
|
@ -204,7 +208,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['toggleAwardRequest', 'promoteCommentToTimelineEvent']),
|
...mapActions(useNotes, ['toggleAwardRequest', 'promoteCommentToTimelineEvent']),
|
||||||
onEdit() {
|
onEdit() {
|
||||||
this.$emit('handleEdit');
|
this.$emit('handleEdit');
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { mapActions, mapState } from 'pinia';
|
||||||
import { mapActions, mapGetters } from 'vuex';
|
|
||||||
import { createAlert } from '~/alert';
|
import { createAlert } from '~/alert';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import AwardsList from '~/vue_shared/components/awards_list.vue';
|
import AwardsList from '~/vue_shared/components/awards_list.vue';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -37,13 +37,13 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['getUserData']),
|
...mapState(useNotes, ['getUserData']),
|
||||||
isAuthoredByMe() {
|
isAuthoredByMe() {
|
||||||
return this.noteAuthorId === this.getUserData.id;
|
return this.noteAuthorId === this.getUserData.id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['toggleAwardRequest']),
|
...mapActions(useNotes, ['toggleAwardRequest']),
|
||||||
handleAward(awardName) {
|
handleAward(awardName) {
|
||||||
const data = {
|
const data = {
|
||||||
endpoint: this.toggleAwardPath,
|
endpoint: this.toggleAwardPath,
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { escape } from 'lodash';
|
import { escape } from 'lodash';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { mapState, mapActions } from 'pinia';
|
||||||
import {
|
|
||||||
mapActions as mapVuexActions,
|
|
||||||
mapGetters as mapVuexGetters,
|
|
||||||
mapState as mapVuexState,
|
|
||||||
} from 'vuex';
|
|
||||||
import { mapState } from 'pinia';
|
|
||||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||||
import { __, sprintf } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
|
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
|
||||||
import { renderGFM } from '~/behaviors/markdown/render_gfm';
|
import { renderGFM } from '~/behaviors/markdown/render_gfm';
|
||||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
import { useMrNotes } from '~/mr_notes/store/legacy_mr_notes';
|
import { useMrNotes } from '~/mr_notes/store/legacy_mr_notes';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
import NoteAttachment from './note_attachment.vue';
|
import NoteAttachment from './note_attachment.vue';
|
||||||
import NoteAwardsList from './note_awards_list.vue';
|
import NoteAwardsList from './note_awards_list.vue';
|
||||||
import NoteEditedText from './note_edited_text.vue';
|
import NoteEditedText from './note_edited_text.vue';
|
||||||
|
@ -72,12 +67,14 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapVuexGetters(['getDiscussion', 'suggestionsCount', 'getSuggestionsFilePaths']),
|
|
||||||
...mapState(useLegacyDiffs, ['suggestionCommitMessage']),
|
...mapState(useLegacyDiffs, ['suggestionCommitMessage']),
|
||||||
...mapState(useMrNotes, ['failedToLoadMetadata']),
|
...mapState(useMrNotes, ['failedToLoadMetadata']),
|
||||||
...mapVuexState({
|
...mapState(useNotes, [
|
||||||
batchSuggestionsInfo: (state) => state.notes.batchSuggestionsInfo,
|
'batchSuggestionsInfo',
|
||||||
}),
|
'getDiscussion',
|
||||||
|
'suggestionsCount',
|
||||||
|
'getSuggestionsFilePaths',
|
||||||
|
]),
|
||||||
discussion() {
|
discussion() {
|
||||||
if (!this.note.isDraft) return {};
|
if (!this.note.isDraft) return {};
|
||||||
|
|
||||||
|
@ -157,7 +154,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapVuexActions([
|
...mapActions(useNotes, [
|
||||||
'submitSuggestion',
|
'submitSuggestion',
|
||||||
'submitSuggestionBatch',
|
'submitSuggestionBatch',
|
||||||
'addSuggestionInfoToBatch',
|
'addSuggestionInfoToBatch',
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlBadge, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
|
import { GlBadge, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { getActivePinia } from 'pinia';
|
||||||
import { mapActions } from 'vuex';
|
|
||||||
import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils';
|
import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
import { TYPE_ACTIVITY, TYPE_COMMENT } from '~/import/constants';
|
import { TYPE_ACTIVITY, TYPE_COMMENT } from '~/import/constants';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
|
@ -129,11 +128,10 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['setTargetNoteHash']),
|
async updateTargetNoteHash() {
|
||||||
updateTargetNoteHash() {
|
if (!getActivePinia()) return;
|
||||||
if (this.$store) {
|
const { useNotes } = await import('~/notes/store/legacy_notes');
|
||||||
this.setTargetNoteHash(this.noteTimestampLink);
|
useNotes().setTargetNoteHash(this.noteTimestampLink);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
handleUsernameMouseEnter() {
|
handleUsernameMouseEnter() {
|
||||||
this.$refs.authorNameLink.dispatchEvent(new Event('mouseenter'));
|
this.$refs.authorNameLink.dispatchEvent(new Event('mouseenter'));
|
||||||
|
|
|
@ -31,9 +31,14 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
tagCountText() {
|
tagCountText() {
|
||||||
if (isEmpty(this.pageInfo)) {
|
if (
|
||||||
|
isEmpty(this.pageInfo) ||
|
||||||
|
!Number.isInteger(this.pageInfo.total) ||
|
||||||
|
this.pageInfo.total === 0
|
||||||
|
) {
|
||||||
return EMPTY_TAG_LABEL;
|
return EMPTY_TAG_LABEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tagsCountText(this.pageInfo.total);
|
return tagsCountText(this.pageInfo.total);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -146,7 +146,7 @@ export const fetchSidebarCount = ({ commit, state }, skipBlobs) => {
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setQuery = async ({ state, commit, getters }, { key, value }) => {
|
export const setQuery = async ({ state, commit, getters, dispatch }, { key, value }) => {
|
||||||
commit(types.SET_QUERY, { key, value });
|
commit(types.SET_QUERY, { key, value });
|
||||||
|
|
||||||
if (SIDEBAR_PARAMS.includes(key)) {
|
if (SIDEBAR_PARAMS.includes(key)) {
|
||||||
|
@ -157,13 +157,32 @@ export const setQuery = async ({ state, commit, getters }, { key, value }) => {
|
||||||
setDataToLS(LS_REGEX_HANDLE, value);
|
setDataToLS(LS_REGEX_HANDLE, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.searchType === SEARCH_TYPE_ZOEKT && getters.currentScope === SCOPE_BLOB) {
|
const isZoektSearch =
|
||||||
const newUrl = setUrlParams({ ...state.query }, window.location.href, false, true);
|
state.searchType === SEARCH_TYPE_ZOEKT && getters.currentScope === SCOPE_BLOB;
|
||||||
|
|
||||||
|
if (isZoektSearch && key === 'search') {
|
||||||
|
const shouldResetPage = state.query?.page > 1 || state.urlQuery?.page > 1;
|
||||||
|
const query = shouldResetPage ? { ...state.query, page: 1 } : { ...state.query };
|
||||||
|
const newUrl = setUrlParams(query, window.location.href, true, true);
|
||||||
document.title = buildDocumentTitle(state.query.search);
|
document.title = buildDocumentTitle(state.query.search);
|
||||||
updateHistory({ state: state.query, title: state.query.search, url: newUrl, replace: false });
|
|
||||||
|
updateHistory({ state: query, title: state.query.search, url: newUrl, replace: true });
|
||||||
|
|
||||||
|
if (shouldResetPage) {
|
||||||
|
commit(types.SET_QUERY, { key: 'page', value: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
fetchSidebarCount({ state, commit });
|
dispatch('fetchSidebarCount');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isZoektSearch && key === 'page') {
|
||||||
|
updateHistory({
|
||||||
|
state: state.query,
|
||||||
|
title: state.query.search,
|
||||||
|
url: setUrlParams({ ...state.query }, window.location.href, true, true),
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
import { GlButton, GlTooltipDirective, GlAnimatedChevronLgDownUpIcon } from '@gitlab/ui';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import { STATUS_CLOSED, STATUS_MERGED } from '~/issues/constants';
|
import { STATUS_CLOSED, STATUS_MERGED } from '~/issues/constants';
|
||||||
import StatusIcon from './mr_widget_status_icon.vue';
|
import StatusIcon from './mr_widget_status_icon.vue';
|
||||||
|
@ -8,6 +8,7 @@ import Actions from './action_buttons.vue';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
GlButton,
|
GlButton,
|
||||||
|
GlAnimatedChevronLgDownUpIcon,
|
||||||
StatusIcon,
|
StatusIcon,
|
||||||
Actions,
|
Actions,
|
||||||
},
|
},
|
||||||
|
@ -120,13 +121,14 @@ export default {
|
||||||
:title="collapsed ? expandDetailsTooltip : collapseDetailsTooltip"
|
:title="collapsed ? expandDetailsTooltip : collapseDetailsTooltip"
|
||||||
:aria-label="collapsed ? expandDetailsTooltip : collapseDetailsTooltip"
|
:aria-label="collapsed ? expandDetailsTooltip : collapseDetailsTooltip"
|
||||||
:aria-expanded="collapsed ? 'false' : 'true'"
|
:aria-expanded="collapsed ? 'false' : 'true'"
|
||||||
:icon="collapsed ? 'chevron-lg-down' : 'chevron-lg-up'"
|
|
||||||
category="tertiary"
|
category="tertiary"
|
||||||
size="small"
|
size="small"
|
||||||
class="gl-align-top"
|
class="btn-icon"
|
||||||
data-testid="widget-toggle"
|
data-testid="widget-toggle"
|
||||||
@click="() => $emit('toggle')"
|
@click="() => $emit('toggle')"
|
||||||
/>
|
>
|
||||||
|
<gl-animated-chevron-lg-down-up-icon :is-on="!collapsed" />
|
||||||
|
</gl-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
<!-- eslint-disable vue/multi-word-component-names -->
|
<!-- eslint-disable vue/multi-word-component-names -->
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlLink, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
|
import {
|
||||||
|
GlButton,
|
||||||
|
GlLink,
|
||||||
|
GlTooltipDirective,
|
||||||
|
GlLoadingIcon,
|
||||||
|
GlAnimatedChevronLgDownUpIcon,
|
||||||
|
} from '@gitlab/ui';
|
||||||
import { kebabCase } from 'lodash';
|
import { kebabCase } from 'lodash';
|
||||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||||
import { normalizeHeaders } from '~/lib/utils/common_utils';
|
import { normalizeHeaders } from '~/lib/utils/common_utils';
|
||||||
|
@ -40,6 +46,7 @@ export default {
|
||||||
GlLink,
|
GlLink,
|
||||||
GlButton,
|
GlButton,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
|
GlAnimatedChevronLgDownUpIcon,
|
||||||
ContentRow,
|
ContentRow,
|
||||||
DynamicContent,
|
DynamicContent,
|
||||||
DynamicScroller,
|
DynamicScroller,
|
||||||
|
@ -443,12 +450,14 @@ export default {
|
||||||
:title="collapseButtonLabel"
|
:title="collapseButtonLabel"
|
||||||
:aria-expanded="`${!isCollapsed}`"
|
:aria-expanded="`${!isCollapsed}`"
|
||||||
:aria-label="collapseButtonLabel"
|
:aria-label="collapseButtonLabel"
|
||||||
:icon="isCollapsed ? 'chevron-lg-down' : 'chevron-lg-up'"
|
|
||||||
category="tertiary"
|
category="tertiary"
|
||||||
data-testid="toggle-button"
|
data-testid="toggle-button"
|
||||||
size="small"
|
size="small"
|
||||||
|
class="btn-icon"
|
||||||
@click="toggleCollapsed"
|
@click="toggleCollapsed"
|
||||||
/>
|
>
|
||||||
|
<gl-animated-chevron-lg-down-up-icon :is-on="!isCollapsed" />
|
||||||
|
</gl-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -399,17 +399,24 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
</gl-filtered-search>
|
</gl-filtered-search>
|
||||||
</div>
|
</div>
|
||||||
<gl-sorting
|
<div
|
||||||
v-if="selectedSortOption"
|
:class="{
|
||||||
:sort-options="transformedSortOptions"
|
'gl-flex gl-items-center gl-justify-between gl-gap-3': $scopedSlots['user-preference'],
|
||||||
:sort-by="sortById"
|
}"
|
||||||
:is-ascending="sortDirectionAscending"
|
>
|
||||||
class="sort-dropdown-container gl-w-full sm:!gl-m-0 sm:gl-w-auto"
|
<slot name="user-preference"></slot>
|
||||||
dropdown-class="gl-grow"
|
<gl-sorting
|
||||||
dropdown-toggle-class="gl-grow"
|
v-if="selectedSortOption"
|
||||||
sort-direction-toggle-class="!gl-shrink !gl-grow-0"
|
:sort-options="transformedSortOptions"
|
||||||
@sortByChange="handleSortByChange"
|
:sort-by="sortById"
|
||||||
@sortDirectionChange="handleSortDirectionChange"
|
:is-ascending="sortDirectionAscending"
|
||||||
/>
|
class="sort-dropdown-container gl-w-full sm:!gl-m-0 sm:gl-w-auto"
|
||||||
|
dropdown-toggle-class="gl-grow"
|
||||||
|
dropdown-class="gl-grow"
|
||||||
|
sort-direction-toggle-class="!gl-shrink !gl-grow-0"
|
||||||
|
@sortByChange="handleSortByChange"
|
||||||
|
@sortDirectionChange="handleSortDirectionChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -355,7 +355,11 @@ export default {
|
||||||
@checked-input="handleAllIssuablesCheckedInput"
|
@checked-input="handleAllIssuablesCheckedInput"
|
||||||
@onFilter="$emit('filter', $event)"
|
@onFilter="$emit('filter', $event)"
|
||||||
@onSort="$emit('sort', $event)"
|
@onSort="$emit('sort', $event)"
|
||||||
/>
|
>
|
||||||
|
<template #user-preference>
|
||||||
|
<slot name="user-preference"></slot>
|
||||||
|
</template>
|
||||||
|
</filtered-search-bar>
|
||||||
<gl-alert
|
<gl-alert
|
||||||
v-if="error"
|
v-if="error"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
GlDisclosureDropdown,
|
||||||
|
GlDisclosureDropdownItem,
|
||||||
|
GlToggle,
|
||||||
|
GlTooltipDirective,
|
||||||
|
} from '@gitlab/ui';
|
||||||
|
import { __, s__ } from '~/locale';
|
||||||
|
import { createAlert } from '~/alert';
|
||||||
|
|
||||||
|
import updateWorkItemsDisplaySettings from '../../graphql/update_user_preferences.mutation.graphql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
GlDisclosureDropdown,
|
||||||
|
GlDisclosureDropdownItem,
|
||||||
|
GlToggle,
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
GlTooltip: GlTooltipDirective,
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
isSignedIn: {
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
i18n: {
|
||||||
|
displayOptions: s__('WorkItems|Display options'),
|
||||||
|
yourPreferences: s__('WorkItems|Your preferences'),
|
||||||
|
openItemsInSidePanel: s__('WorkItems|Open items in side panel'),
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
displaySettings: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isDropdownVisible: false,
|
||||||
|
isLoading: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
tooltipText() {
|
||||||
|
return !this.isDropdownVisible ? this.$options.i18n.displayOptions : '';
|
||||||
|
},
|
||||||
|
shouldOpenItemsInSidePanel() {
|
||||||
|
return this.displaySettings?.shouldOpenItemsInSidePanel ?? true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showDropdown() {
|
||||||
|
this.isDropdownVisible = true;
|
||||||
|
},
|
||||||
|
hideDropdown() {
|
||||||
|
this.isDropdownVisible = false;
|
||||||
|
},
|
||||||
|
async toggleSidePanelPreference() {
|
||||||
|
const newDisplaySettings = {
|
||||||
|
...this.displaySettings,
|
||||||
|
shouldOpenItemsInSidePanel: !this.shouldOpenItemsInSidePanel,
|
||||||
|
};
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
workItemsDisplaySettings: newDisplaySettings,
|
||||||
|
};
|
||||||
|
this.isLoading = true;
|
||||||
|
try {
|
||||||
|
await this.$apollo.mutate({
|
||||||
|
mutation: updateWorkItemsDisplaySettings,
|
||||||
|
variables: {
|
||||||
|
input,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$emit('displaySettingsChanged', newDisplaySettings);
|
||||||
|
} catch (error) {
|
||||||
|
createAlert({
|
||||||
|
message: __('Something went wrong while saving the preference.'),
|
||||||
|
captureError: true,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<gl-disclosure-dropdown
|
||||||
|
v-if="isSignedIn"
|
||||||
|
v-gl-tooltip="tooltipText"
|
||||||
|
icon="preferences"
|
||||||
|
text-sr-only
|
||||||
|
:toggle-text="$options.i18n.displayOptions"
|
||||||
|
category="primary"
|
||||||
|
no-caret
|
||||||
|
placement="bottom-end"
|
||||||
|
:auto-close="false"
|
||||||
|
class="gl-mt-[10px] sm:gl-mt-0"
|
||||||
|
@shown="showDropdown"
|
||||||
|
@hidden="hideDropdown"
|
||||||
|
>
|
||||||
|
<div class="gl-mt-2">
|
||||||
|
<span class="gl-pl-4 gl-text-sm gl-font-bold">{{ $options.i18n.yourPreferences }}</span>
|
||||||
|
<gl-disclosure-dropdown-item
|
||||||
|
class="work-item-dropdown-toggle"
|
||||||
|
@action="toggleSidePanelPreference"
|
||||||
|
>
|
||||||
|
<template #list-item>
|
||||||
|
<gl-toggle
|
||||||
|
:value="shouldOpenItemsInSidePanel"
|
||||||
|
:label="$options.i18n.openItemsInSidePanel"
|
||||||
|
class="gl-justify-between"
|
||||||
|
label-position="left"
|
||||||
|
:is-loading="isLoading"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</gl-disclosure-dropdown-item>
|
||||||
|
</div>
|
||||||
|
</gl-disclosure-dropdown>
|
||||||
|
</template>
|
|
@ -0,0 +1,8 @@
|
||||||
|
query getUserWorkItemsDisplaySettingsPreferences {
|
||||||
|
currentUser {
|
||||||
|
id
|
||||||
|
userPreferences {
|
||||||
|
workItemsDisplaySettings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
mutation updateWorkItemsDisplaySettings($input: UserPreferencesUpdateInput!) {
|
||||||
|
userPreferencesUpdate(input: $input) {
|
||||||
|
userPreferences {
|
||||||
|
workItemsDisplaySettings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -91,6 +91,7 @@ import CreateWorkItemModal from '../components/create_work_item_modal.vue';
|
||||||
import WorkItemHealthStatus from '../components/work_item_health_status.vue';
|
import WorkItemHealthStatus from '../components/work_item_health_status.vue';
|
||||||
import WorkItemDrawer from '../components/work_item_drawer.vue';
|
import WorkItemDrawer from '../components/work_item_drawer.vue';
|
||||||
import WorkItemListHeading from '../components/work_item_list_heading.vue';
|
import WorkItemListHeading from '../components/work_item_list_heading.vue';
|
||||||
|
import WorkItemUserPreferences from '../components/shared/work_item_list_preferences.vue';
|
||||||
import {
|
import {
|
||||||
BASE_ALLOWED_CREATE_TYPES,
|
BASE_ALLOWED_CREATE_TYPES,
|
||||||
DETAIL_VIEW_QUERY_PARAM_NAME,
|
DETAIL_VIEW_QUERY_PARAM_NAME,
|
||||||
|
@ -102,6 +103,7 @@ import {
|
||||||
WORK_ITEM_TYPE_NAME_KEY_RESULT,
|
WORK_ITEM_TYPE_NAME_KEY_RESULT,
|
||||||
WORK_ITEM_TYPE_NAME_OBJECTIVE,
|
WORK_ITEM_TYPE_NAME_OBJECTIVE,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
|
import getUserWorkItemsDisplaySettingsPreferences from '../graphql/get_user_preferences.query.graphql';
|
||||||
|
|
||||||
const EmojiToken = () =>
|
const EmojiToken = () =>
|
||||||
import('~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue');
|
import('~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue');
|
||||||
|
@ -135,6 +137,7 @@ export default {
|
||||||
CreateWorkItemModal,
|
CreateWorkItemModal,
|
||||||
LocalBoard,
|
LocalBoard,
|
||||||
WorkItemListHeading,
|
WorkItemListHeading,
|
||||||
|
WorkItemUserPreferences,
|
||||||
},
|
},
|
||||||
mixins: [glFeatureFlagMixin()],
|
mixins: [glFeatureFlagMixin()],
|
||||||
inject: [
|
inject: [
|
||||||
|
@ -204,9 +207,27 @@ export default {
|
||||||
initialLoadWasFiltered: false,
|
initialLoadWasFiltered: false,
|
||||||
showLocalBoard: false,
|
showLocalBoard: false,
|
||||||
namespaceId: null,
|
namespaceId: null,
|
||||||
|
displaySettings: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
|
displaySettings: {
|
||||||
|
query: getUserWorkItemsDisplaySettingsPreferences,
|
||||||
|
update(data) {
|
||||||
|
return (
|
||||||
|
data?.currentUser?.userPreferences?.workItemsDisplaySettings ?? {
|
||||||
|
shouldOpenItemsInSidePanel: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
skip() {
|
||||||
|
return !this.isSignedIn;
|
||||||
|
},
|
||||||
|
error(error) {
|
||||||
|
this.error = __('An error occurred while getting work item user preference.');
|
||||||
|
Sentry.captureException(error);
|
||||||
|
},
|
||||||
|
},
|
||||||
workItemsFull: {
|
workItemsFull: {
|
||||||
query() {
|
query() {
|
||||||
return getWorkItemsQuery;
|
return getWorkItemsQuery;
|
||||||
|
@ -335,6 +356,8 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
workItemDrawerEnabled() {
|
workItemDrawerEnabled() {
|
||||||
|
const shouldOpenItemsInSidePanel = this.displaySettings?.shouldOpenItemsInSidePanel ?? true;
|
||||||
|
if (!shouldOpenItemsInSidePanel) return false;
|
||||||
if (this.glFeatures.workItemViewForIssues) {
|
if (this.glFeatures.workItemViewForIssues) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -950,6 +973,9 @@ export default {
|
||||||
this.isInitialLoadComplete = false;
|
this.isInitialLoadComplete = false;
|
||||||
this.refetchItems({ refetchCounts: true });
|
this.refetchItems({ refetchCounts: true });
|
||||||
},
|
},
|
||||||
|
handleDisplaySettingsChanged(newDisplaySettings) {
|
||||||
|
this.displaySettings = newDisplaySettings;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1009,7 +1035,14 @@ export default {
|
||||||
@previous-page="handlePreviousPage"
|
@previous-page="handlePreviousPage"
|
||||||
@sort="handleSort"
|
@sort="handleSort"
|
||||||
@select-issuable="handleToggle"
|
@select-issuable="handleToggle"
|
||||||
|
@displaySettingsChanged="handleDisplaySettingsChanged"
|
||||||
>
|
>
|
||||||
|
<template #user-preference>
|
||||||
|
<work-item-user-preferences
|
||||||
|
:display-settings="displaySettings"
|
||||||
|
@displaySettingsChanged="handleDisplaySettingsChanged"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<template v-if="!isPlanningViewsEnabled" #nav-actions>
|
<template v-if="!isPlanningViewsEnabled" #nav-actions>
|
||||||
<div class="gl-flex gl-gap-3">
|
<div class="gl-flex gl-gap-3">
|
||||||
<gl-button
|
<gl-button
|
||||||
|
|
|
@ -442,6 +442,7 @@ class ProjectPolicy < BasePolicy
|
||||||
|
|
||||||
rule { can?(:reporter_access) }.policy do
|
rule { can?(:reporter_access) }.policy do
|
||||||
enable :admin_issue_board
|
enable :admin_issue_board
|
||||||
|
enable :read_code
|
||||||
enable :download_code
|
enable :download_code
|
||||||
enable :read_statistics
|
enable :read_statistics
|
||||||
enable :daily_statistics
|
enable :daily_statistics
|
||||||
|
@ -896,6 +897,7 @@ class ProjectPolicy < BasePolicy
|
||||||
rule { repository_disabled }.policy do
|
rule { repository_disabled }.policy do
|
||||||
prevent :build_push_code
|
prevent :build_push_code
|
||||||
prevent :push_code
|
prevent :push_code
|
||||||
|
prevent :read_code
|
||||||
prevent :download_code
|
prevent :download_code
|
||||||
prevent :build_download_code
|
prevent :build_download_code
|
||||||
prevent :fork_project
|
prevent :fork_project
|
||||||
|
@ -996,6 +998,7 @@ class ProjectPolicy < BasePolicy
|
||||||
enable :read_deployment
|
enable :read_deployment
|
||||||
enable :read_commit_status
|
enable :read_commit_status
|
||||||
enable :read_container_image
|
enable :read_container_image
|
||||||
|
enable :read_code
|
||||||
enable :download_code
|
enable :download_code
|
||||||
enable :read_release
|
enable :read_release
|
||||||
enable :download_wiki_code
|
enable :download_wiki_code
|
||||||
|
@ -1093,6 +1096,7 @@ class ProjectPolicy < BasePolicy
|
||||||
|
|
||||||
rule { download_code_deploy_key }.policy do
|
rule { download_code_deploy_key }.policy do
|
||||||
enable :download_code
|
enable :download_code
|
||||||
|
enable :read_code
|
||||||
end
|
end
|
||||||
|
|
||||||
rule { push_code_deploy_key }.policy do
|
rule { push_code_deploy_key }.policy do
|
||||||
|
@ -1207,10 +1211,6 @@ class ProjectPolicy < BasePolicy
|
||||||
enable :read_project_metadata
|
enable :read_project_metadata
|
||||||
end
|
end
|
||||||
|
|
||||||
rule { can?(:download_code) }.policy do
|
|
||||||
enable :read_code
|
|
||||||
end
|
|
||||||
|
|
||||||
rule { can?(:developer_access) & namespace_catalog_available }.policy do
|
rule { can?(:developer_access) & namespace_catalog_available }.policy do
|
||||||
enable :read_namespace_catalog
|
enable :read_namespace_catalog
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ module Integrations
|
||||||
end
|
end
|
||||||
|
|
||||||
expose :tags do |item|
|
expose :tags do |item|
|
||||||
item['tags'].map { |tag| strip_tags(tag['name']) }
|
item['tags'].blank? ? [] : item['tags'].map { |tag| strip_tags(tag['name']) }
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -13,14 +13,15 @@ class MergeRequests::HandleAssigneesChangeWorker
|
||||||
idempotent!
|
idempotent!
|
||||||
|
|
||||||
def perform(merge_request_id, user_id, old_assignee_ids, options = {})
|
def perform(merge_request_id, user_id, old_assignee_ids, options = {})
|
||||||
merge_request = MergeRequest.find(merge_request_id)
|
merge_request = MergeRequest.find_by_id(merge_request_id)
|
||||||
user = User.find(user_id)
|
user = User.find_by_id(user_id)
|
||||||
|
|
||||||
|
return unless merge_request && user
|
||||||
|
|
||||||
old_assignees = User.id_in(old_assignee_ids)
|
old_assignees = User.id_in(old_assignee_ids)
|
||||||
|
|
||||||
::MergeRequests::HandleAssigneesChangeService
|
::MergeRequests::HandleAssigneesChangeService
|
||||||
.new(project: merge_request.target_project, current_user: user)
|
.new(project: merge_request.target_project, current_user: user)
|
||||||
.execute(merge_request, old_assignees, options)
|
.execute(merge_request, old_assignees, options)
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
- 1
|
- 1
|
||||||
- - admin_emails
|
- - admin_emails
|
||||||
- 1
|
- 1
|
||||||
|
- - ai_active_context_code_repository_index
|
||||||
|
- 1
|
||||||
- - ai_active_context_code_saas_initial_indexing_event
|
- - ai_active_context_code_saas_initial_indexing_event
|
||||||
- 1
|
- 1
|
||||||
- - ai_knowledge_graph_indexing_task
|
- - ai_knowledge_graph_indexing_task
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ChangeWorkItemCustomLifecycleNameLimit < Gitlab::Database::Migration[2.3]
|
||||||
|
milestone '18.2'
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
CONSTRAINT_NAME = 'check_1feff2de99'
|
||||||
|
|
||||||
|
def up
|
||||||
|
remove_text_limit :work_item_custom_lifecycles, :name, constraint_name: CONSTRAINT_NAME
|
||||||
|
add_text_limit :work_item_custom_lifecycles, :name, 64, constraint_name: CONSTRAINT_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_text_limit :work_item_custom_lifecycles, :name, constraint_name: CONSTRAINT_NAME
|
||||||
|
add_text_limit :work_item_custom_lifecycles, :name, 255, constraint_name: CONSTRAINT_NAME
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ChangeWorkItemCustomStatusDescriptionLimit < Gitlab::Database::Migration[2.3]
|
||||||
|
milestone '18.2'
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
CONSTRAINT_NAME = 'check_8ea8b3c991'
|
||||||
|
|
||||||
|
def up
|
||||||
|
remove_text_limit :work_item_custom_statuses, :description, constraint_name: CONSTRAINT_NAME
|
||||||
|
add_text_limit :work_item_custom_statuses, :description, 128, constraint_name: CONSTRAINT_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_text_limit :work_item_custom_statuses, :description, constraint_name: CONSTRAINT_NAME
|
||||||
|
add_text_limit :work_item_custom_statuses, :description, 255, constraint_name: CONSTRAINT_NAME
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
443d16f76f1a3b90e71b697a443466779146c6f2a20946b58801dda73fd30d06
|
|
@ -0,0 +1 @@
|
||||||
|
c14b80337285a053b5b2d7009adca6515caad99d477cc8c106765f3751938da2
|
|
@ -26122,7 +26122,7 @@ CREATE TABLE work_item_custom_lifecycles (
|
||||||
name text NOT NULL,
|
name text NOT NULL,
|
||||||
created_by_id bigint,
|
created_by_id bigint,
|
||||||
updated_by_id bigint,
|
updated_by_id bigint,
|
||||||
CONSTRAINT check_1feff2de99 CHECK ((char_length(name) <= 255))
|
CONSTRAINT check_1feff2de99 CHECK ((char_length(name) <= 64))
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE SEQUENCE work_item_custom_lifecycles_id_seq
|
CREATE SEQUENCE work_item_custom_lifecycles_id_seq
|
||||||
|
@ -26148,7 +26148,7 @@ CREATE TABLE work_item_custom_statuses (
|
||||||
converted_from_system_defined_status_identifier smallint,
|
converted_from_system_defined_status_identifier smallint,
|
||||||
CONSTRAINT check_4789467800 CHECK ((char_length(color) <= 7)),
|
CONSTRAINT check_4789467800 CHECK ((char_length(color) <= 7)),
|
||||||
CONSTRAINT check_720a7c4d24 CHECK ((char_length(name) <= 32)),
|
CONSTRAINT check_720a7c4d24 CHECK ((char_length(name) <= 32)),
|
||||||
CONSTRAINT check_8ea8b3c991 CHECK ((char_length(description) <= 255)),
|
CONSTRAINT check_8ea8b3c991 CHECK ((char_length(description) <= 128)),
|
||||||
CONSTRAINT check_ff2bac1606 CHECK ((category > 0))
|
CONSTRAINT check_ff2bac1606 CHECK ((category > 0))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ The namespace is a user or group in GitLab, such as `gitlab.com/sidney-jones` or
|
||||||
|
|
||||||
Using the GitLab UI, the GitHub importer always imports from the
|
Using the GitLab UI, the GitHub importer always imports from the
|
||||||
`github.com` domain. If you are importing from a self-hosted GitHub Enterprise Server domain, use the
|
`github.com` domain. If you are importing from a self-hosted GitHub Enterprise Server domain, use the
|
||||||
[GitLab Import API](#use-the-api) GitHub endpoint.
|
[GitLab Import API](#use-the-api) GitHub endpoint with a GitLab access token with the `api` scope.
|
||||||
|
|
||||||
You can change the target namespace and target repository name before you import.
|
You can change the target namespace and target repository name before you import.
|
||||||
|
|
||||||
|
|
|
@ -7252,6 +7252,9 @@ msgstr ""
|
||||||
msgid "An error occurred while getting merge request counts"
|
msgid "An error occurred while getting merge request counts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "An error occurred while getting work item user preference."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "An error occurred while initializing path locks"
|
msgid "An error occurred while initializing path locks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -59713,6 +59716,9 @@ msgstr ""
|
||||||
msgid "Something went wrong while resolving this discussion. Please try again."
|
msgid "Something went wrong while resolving this discussion. Please try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Something went wrong while saving the preference."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Something went wrong while setting %{issuableType} %{dateType} date."
|
msgid "Something went wrong while setting %{issuableType} %{dateType} date."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -70208,6 +70214,15 @@ msgstr ""
|
||||||
msgid "WorkItems|Ancestors not available"
|
msgid "WorkItems|Ancestors not available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "WorkItems|Display options"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "WorkItems|Open items in side panel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "WorkItems|Your preferences"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "WorkItem| %{workItemType}s closed"
|
msgid "WorkItem| %{workItemType}s closed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -326,5 +326,6 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.22.1",
|
"node": ">=12.22.1",
|
||||||
"yarn": "^1.10.0"
|
"yarn": "^1.10.0"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,27 @@
|
||||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
import Vue from 'vue';
|
||||||
|
import { PiniaVuePlugin } from 'pinia';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
import PreviewItem from '~/batch_comments/components/preview_item.vue';
|
import PreviewItem from '~/batch_comments/components/preview_item.vue';
|
||||||
import store from '~/mr_notes/stores';
|
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
|
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||||
import { createDraft } from '../mock_data';
|
import { createDraft } from '../mock_data';
|
||||||
|
|
||||||
jest.mock('~/behaviors/markdown/render_gfm');
|
jest.mock('~/behaviors/markdown/render_gfm');
|
||||||
jest.mock('~/mr_notes/stores', () => jest.requireActual('helpers/mocks/mr_notes/stores'));
|
jest.mock('~/mr_notes/stores', () => jest.requireActual('helpers/mocks/mr_notes/stores'));
|
||||||
|
|
||||||
|
Vue.use(PiniaVuePlugin);
|
||||||
|
|
||||||
describe('Batch comments draft preview item component', () => {
|
describe('Batch comments draft preview item component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let pinia;
|
||||||
let draft;
|
let draft;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.reset();
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
||||||
|
useLegacyDiffs();
|
||||||
store.getters.getDiscussion = jest.fn(() => null);
|
useNotes();
|
||||||
});
|
});
|
||||||
|
|
||||||
function createComponent(extra = {}, improvedReviewExperience = false) {
|
function createComponent(extra = {}, improvedReviewExperience = false) {
|
||||||
|
@ -23,9 +31,7 @@ describe('Batch comments draft preview item component', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
wrapper = mountExtended(PreviewItem, {
|
wrapper = mountExtended(PreviewItem, {
|
||||||
mocks: {
|
pinia,
|
||||||
$store: store,
|
|
||||||
},
|
|
||||||
propsData: { draft },
|
propsData: { draft },
|
||||||
provide: {
|
provide: {
|
||||||
glFeatures: { improvedReviewExperience },
|
glFeatures: { improvedReviewExperience },
|
||||||
|
@ -93,17 +99,18 @@ describe('Batch comments draft preview item component', () => {
|
||||||
|
|
||||||
describe('for thread', () => {
|
describe('for thread', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.getters.getDiscussion.mockReturnValue({
|
useNotes().discussions = [
|
||||||
id: '1',
|
{
|
||||||
notes: [
|
id: '1',
|
||||||
{
|
notes: [
|
||||||
author: {
|
{
|
||||||
name: "Author 'Nick' Name",
|
author: {
|
||||||
|
name: "Author 'Nick' Name",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
});
|
];
|
||||||
store.getters.isDiscussionResolved = jest.fn().mockReturnValue(false);
|
|
||||||
|
|
||||||
createComponent({ discussion_id: '1', resolve_discussion: true });
|
createComponent({ discussion_id: '1', resolve_discussion: true });
|
||||||
});
|
});
|
||||||
|
|
|
@ -91,6 +91,7 @@ describe('Incident Tabs component', () => {
|
||||||
const findSummaryTab = () => wrapper.findByTestId('summary-tab');
|
const findSummaryTab = () => wrapper.findByTestId('summary-tab');
|
||||||
const findTimelineTab = () => wrapper.findByTestId('timeline-tab');
|
const findTimelineTab = () => wrapper.findByTestId('timeline-tab');
|
||||||
const findAlertDetailsTab = () => wrapper.findByTestId('alert-details-tab');
|
const findAlertDetailsTab = () => wrapper.findByTestId('alert-details-tab');
|
||||||
|
const findMetricsTab = () => wrapper.findByTestId('metrics-tab');
|
||||||
const findAlertDetailsComponent = () => wrapper.findComponent(AlertDetailsTable);
|
const findAlertDetailsComponent = () => wrapper.findComponent(AlertDetailsTable);
|
||||||
const findDescriptionComponent = () => wrapper.findComponent(DescriptionComponent);
|
const findDescriptionComponent = () => wrapper.findComponent(DescriptionComponent);
|
||||||
const findHighlightBarComponent = () => wrapper.findComponent(HighlightBar);
|
const findHighlightBarComponent = () => wrapper.findComponent(HighlightBar);
|
||||||
|
@ -227,5 +228,14 @@ describe('Incident Tabs component', () => {
|
||||||
it('does not render the timeline tab', () => {
|
it('does not render the timeline tab', () => {
|
||||||
expect(findTimelineTab().exists()).toBe(false);
|
expect(findTimelineTab().exists()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not render the alert details tab', () => {
|
||||||
|
mountComponent({ hasLinkedAlerts: true });
|
||||||
|
expect(findAlertDetailsTab().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render the metrics tab', () => {
|
||||||
|
expect(findMetricsTab().exists()).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
import { nextTick } from 'vue';
|
import Vue, { nextTick } from 'vue';
|
||||||
import { GlAvatar, GlAvatarLink } from '@gitlab/ui';
|
import { GlAvatar, GlAvatarLink } from '@gitlab/ui';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { PiniaVuePlugin } from 'pinia';
|
||||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import diffDiscussionHeader from '~/notes/components/diff_discussion_header.vue';
|
import diffDiscussionHeader from '~/notes/components/diff_discussion_header.vue';
|
||||||
import ToggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue';
|
import ToggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue';
|
||||||
import createStore from '~/notes/stores';
|
|
||||||
|
|
||||||
import mockDiffFile from 'jest/diffs/mock_data/diff_discussions';
|
import mockDiffFile from 'jest/diffs/mock_data/diff_discussions';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||||
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
import { discussionMock } from '../mock_data';
|
import { discussionMock } from '../mock_data';
|
||||||
|
|
||||||
|
Vue.use(PiniaVuePlugin);
|
||||||
|
|
||||||
describe('diff_discussion_header component', () => {
|
describe('diff_discussion_header component', () => {
|
||||||
let store;
|
let pinia;
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
const createComponent = ({ propsData = {} } = {}) => {
|
const createComponent = ({ propsData = {} } = {}) => {
|
||||||
wrapper = shallowMountExtended(diffDiscussionHeader, {
|
wrapper = shallowMountExtended(diffDiscussionHeader, {
|
||||||
store,
|
pinia,
|
||||||
propsData: {
|
propsData: {
|
||||||
discussion: discussionMock,
|
discussion: discussionMock,
|
||||||
...propsData,
|
...propsData,
|
||||||
|
@ -24,8 +29,9 @@ describe('diff_discussion_header component', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
window.mrTabs = {};
|
window.mrTabs = {};
|
||||||
store = createStore();
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
||||||
|
useLegacyDiffs();
|
||||||
|
useNotes();
|
||||||
createComponent({ propsData: { discussion: discussionMock } });
|
createComponent({ propsData: { discussion: discussionMock } });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,10 +66,6 @@ describe('diff_discussion_header component', () => {
|
||||||
let commitElement;
|
let commitElement;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
store.state.diffs = {
|
|
||||||
projectPath: 'something',
|
|
||||||
};
|
|
||||||
|
|
||||||
createComponent({
|
createComponent({
|
||||||
propsData: {
|
propsData: {
|
||||||
discussion: {
|
discussion: {
|
||||||
|
@ -202,5 +204,18 @@ describe('diff_discussion_header component', () => {
|
||||||
});
|
});
|
||||||
expect(wrapper.findComponent(ToggleRepliesWidget).props('collapsed')).toBe(true);
|
expect(wrapper.findComponent(ToggleRepliesWidget).props('collapsed')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('toggles discussion', () => {
|
||||||
|
createComponent({
|
||||||
|
propsData: {
|
||||||
|
discussion: {
|
||||||
|
...discussionMock,
|
||||||
|
expanded: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
wrapper.findComponent(ToggleRepliesWidget).vm.$emit('toggle');
|
||||||
|
expect(useNotes().toggleDiscussion).toHaveBeenCalledWith({ discussionId: discussionMock.id });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,17 +5,16 @@ import { PiniaVuePlugin } from 'pinia';
|
||||||
import { createTestingPinia } from '@pinia/testing';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import discussionFixture from 'test_fixtures/merge_requests/diff_discussion.json';
|
import discussionFixture from 'test_fixtures/merge_requests/diff_discussion.json';
|
||||||
import imageDiscussionFixture from 'test_fixtures/merge_requests/image_diff_discussion.json';
|
import imageDiscussionFixture from 'test_fixtures/merge_requests/image_diff_discussion.json';
|
||||||
import { createStore } from '~/mr_notes/stores';
|
|
||||||
import DiffWithNote from '~/notes/components/diff_with_note.vue';
|
import DiffWithNote from '~/notes/components/diff_with_note.vue';
|
||||||
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
|
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
|
||||||
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
|
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
|
||||||
import { globalAccessorPlugin } from '~/pinia/plugins';
|
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
|
||||||
Vue.use(PiniaVuePlugin);
|
Vue.use(PiniaVuePlugin);
|
||||||
|
|
||||||
describe('diff_with_note', () => {
|
describe('diff_with_note', () => {
|
||||||
let store;
|
|
||||||
let pinia;
|
let pinia;
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
|
@ -37,7 +36,6 @@ describe('diff_with_note', () => {
|
||||||
const createComponent = (propsData) => {
|
const createComponent = (propsData) => {
|
||||||
wrapper = shallowMount(DiffWithNote, {
|
wrapper = shallowMount(DiffWithNote, {
|
||||||
propsData,
|
propsData,
|
||||||
store,
|
|
||||||
pinia,
|
pinia,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -45,15 +43,7 @@ describe('diff_with_note', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
||||||
useLegacyDiffs();
|
useLegacyDiffs();
|
||||||
store = createStore();
|
useNotes().noteableData = { current_user: {} };
|
||||||
store.replaceState({
|
|
||||||
...store.state,
|
|
||||||
notes: {
|
|
||||||
noteableData: {
|
|
||||||
current_user: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('text diff', () => {
|
describe('text diff', () => {
|
||||||
|
@ -199,7 +189,7 @@ describe('diff_with_note', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = shallowMount(DiffWithNote, {
|
wrapper = shallowMount(DiffWithNote, {
|
||||||
propsData: { discussion: fileDiscussion, diffFile: {} },
|
propsData: { discussion: fileDiscussion, diffFile: {} },
|
||||||
store,
|
pinia,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -218,7 +208,7 @@ describe('diff_with_note', () => {
|
||||||
|
|
||||||
wrapper = shallowMount(DiffWithNote, {
|
wrapper = shallowMount(DiffWithNote, {
|
||||||
propsData: { discussion: fileDiscussion, diffFile: {} },
|
propsData: { discussion: fileDiscussion, diffFile: {} },
|
||||||
store,
|
pinia,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { getByRole } from '@testing-library/dom';
|
import { getByRole } from '@testing-library/dom';
|
||||||
import { shallowMount, mount } from '@vue/test-utils';
|
import { shallowMount, mount } from '@vue/test-utils';
|
||||||
import { nextTick } from 'vue';
|
import Vue, { nextTick } from 'vue';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { PiniaVuePlugin } from 'pinia';
|
||||||
import DiscussionNotes from '~/notes/components/discussion_notes.vue';
|
import DiscussionNotes from '~/notes/components/discussion_notes.vue';
|
||||||
import NoteableNote from '~/notes/components/noteable_note.vue';
|
import NoteableNote from '~/notes/components/noteable_note.vue';
|
||||||
import { SYSTEM_NOTE } from '~/notes/constants';
|
import { SYSTEM_NOTE } from '~/notes/constants';
|
||||||
|
@ -8,6 +10,9 @@ import createStore from '~/notes/stores';
|
||||||
import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
|
import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
|
||||||
import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
|
import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
|
||||||
import SystemNote from '~/vue_shared/components/notes/system_note.vue';
|
import SystemNote from '~/vue_shared/components/notes/system_note.vue';
|
||||||
|
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
|
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
|
||||||
|
|
||||||
jest.mock('~/behaviors/markdown/render_gfm');
|
jest.mock('~/behaviors/markdown/render_gfm');
|
||||||
|
@ -20,14 +25,18 @@ const DISCUSSION_WITH_LINE_RANGE = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Vue.use(PiniaVuePlugin);
|
||||||
|
|
||||||
describe('DiscussionNotes', () => {
|
describe('DiscussionNotes', () => {
|
||||||
let store;
|
let store;
|
||||||
|
let pinia;
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
const getList = () => getByRole(wrapper.element, 'list');
|
const getList = () => getByRole(wrapper.element, 'list');
|
||||||
const createComponent = (props, mountingMethod = shallowMount) => {
|
const createComponent = (props, mountingMethod = shallowMount) => {
|
||||||
wrapper = mountingMethod(DiscussionNotes, {
|
wrapper = mountingMethod(DiscussionNotes, {
|
||||||
store,
|
store,
|
||||||
|
pinia,
|
||||||
propsData: {
|
propsData: {
|
||||||
discussion: discussionMock,
|
discussion: discussionMock,
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
|
@ -48,6 +57,9 @@ describe('DiscussionNotes', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
||||||
|
useLegacyDiffs();
|
||||||
|
useNotes();
|
||||||
store = createStore();
|
store = createStore();
|
||||||
store.dispatch('setNoteableData', noteableDataMock);
|
store.dispatch('setNoteableData', noteableDataMock);
|
||||||
store.dispatch('setNotesData', notesDataMock);
|
store.dispatch('setNotesData', notesDataMock);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { GlCollapsibleListbox, GlListboxItem, GlButton } from '@gitlab/ui';
|
import { GlCollapsibleListbox, GlListboxItem, GlButton } from '@gitlab/ui';
|
||||||
import Vue, { nextTick } from 'vue';
|
import Vue, { nextTick } from 'vue';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { PiniaVuePlugin } from 'pinia';
|
||||||
import Vuex from 'vuex';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import { mockTracking } from 'helpers/tracking_helper';
|
import { mockTracking } from 'helpers/tracking_helper';
|
||||||
import DiscussionFilter from '~/notes/components/mr_discussion_filter.vue';
|
import DiscussionFilter from '~/notes/components/mr_discussion_filter.vue';
|
||||||
|
@ -11,36 +11,19 @@ import {
|
||||||
MR_FILTER_TRACKING_USER_COMMENTS,
|
MR_FILTER_TRACKING_USER_COMMENTS,
|
||||||
MR_FILTER_TRACKING_BOT_COMMENTS,
|
MR_FILTER_TRACKING_BOT_COMMENTS,
|
||||||
} from '~/notes/constants';
|
} from '~/notes/constants';
|
||||||
|
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||||
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(PiniaVuePlugin);
|
||||||
|
|
||||||
describe('Merge request discussion filter component', () => {
|
describe('Merge request discussion filter component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let store;
|
let pinia;
|
||||||
let updateMergeRequestFilters;
|
|
||||||
let setDiscussionSortDirection;
|
|
||||||
|
|
||||||
function createComponent(mergeRequestFilters = MR_FILTER_OPTIONS.map((f) => f.value)) {
|
|
||||||
updateMergeRequestFilters = jest.fn();
|
|
||||||
setDiscussionSortDirection = jest.fn();
|
|
||||||
|
|
||||||
store = new Vuex.Store({
|
|
||||||
modules: {
|
|
||||||
notes: {
|
|
||||||
state: {
|
|
||||||
mergeRequestFilters,
|
|
||||||
discussionSortOrder: 'asc',
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
updateMergeRequestFilters,
|
|
||||||
setDiscussionSortDirection,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
function createComponent() {
|
||||||
wrapper = mountExtended(DiscussionFilter, {
|
wrapper = mountExtended(DiscussionFilter, {
|
||||||
store,
|
pinia,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +31,13 @@ describe('Merge request discussion filter component', () => {
|
||||||
return wrapper.findComponentByTestId(`listbox-item-${value}`);
|
return wrapper.findComponentByTestId(`listbox-item-${value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
||||||
|
useLegacyDiffs();
|
||||||
|
useNotes().mergeRequestFilters = MR_FILTER_OPTIONS.map((f) => f.value);
|
||||||
|
useNotes().discussionSortOrder = 'asc';
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
localStorage.removeItem('mr_activity_filters');
|
localStorage.removeItem('mr_activity_filters');
|
||||||
localStorage.removeItem('sort_direction_merge_request');
|
localStorage.removeItem('sort_direction_merge_request');
|
||||||
|
@ -59,7 +49,7 @@ describe('Merge request discussion filter component', () => {
|
||||||
|
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
expect(setDiscussionSortDirection).toHaveBeenCalledWith(expect.anything(), {
|
expect(useNotes().setDiscussionSortDirection).toHaveBeenCalledWith({
|
||||||
direction: 'desc',
|
direction: 'desc',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -71,7 +61,7 @@ describe('Merge request discussion filter component', () => {
|
||||||
|
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
expect(updateMergeRequestFilters).toHaveBeenCalledWith(expect.anything(), ['comments']);
|
expect(useNotes().updateMergeRequestFilters).toHaveBeenCalledWith(['comments']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,7 +80,7 @@ describe('Merge request discussion filter component', () => {
|
||||||
|
|
||||||
wrapper.findComponent(GlCollapsibleListbox).vm.$emit('hidden');
|
wrapper.findComponent(GlCollapsibleListbox).vm.$emit('hidden');
|
||||||
|
|
||||||
expect(updateMergeRequestFilters).toHaveBeenCalledWith(expect.anything(), [
|
expect(useNotes().updateMergeRequestFilters).toHaveBeenCalledWith([
|
||||||
'assignees_reviewers',
|
'assignees_reviewers',
|
||||||
'bot_comments',
|
'bot_comments',
|
||||||
'comments',
|
'comments',
|
||||||
|
@ -113,7 +103,7 @@ describe('Merge request discussion filter component', () => {
|
||||||
`('updates toggle text to $expectedText with $state', async ({ state, expectedText }) => {
|
`('updates toggle text to $expectedText with $state', async ({ state, expectedText }) => {
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
store.state.notes.mergeRequestFilters = state;
|
useNotes().mergeRequestFilters = state;
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
|
@ -193,7 +183,7 @@ describe('Merge request discussion filter component', () => {
|
||||||
`(
|
`(
|
||||||
'has the correct attributes and props when the sort-order is "$sortOrder"',
|
'has the correct attributes and props when the sort-order is "$sortOrder"',
|
||||||
async ({ sortOrder, expectedTitle, expectedIcon }) => {
|
async ({ sortOrder, expectedTitle, expectedIcon }) => {
|
||||||
store.state.notes.discussionSortOrder = sortOrder;
|
useNotes().discussionSortOrder = sortOrder;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
const sortDirectionButton = wrapper.findByTestId('mr-discussion-sort-direction');
|
const sortDirectionButton = wrapper.findByTestId('mr-discussion-sort-direction');
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
import { GlFormSelect } from '@gitlab/ui';
|
import { GlFormSelect, GlSprintf } from '@gitlab/ui';
|
||||||
import { mount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { PiniaVuePlugin } from 'pinia';
|
||||||
import Vuex from 'vuex';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import MultilineCommentForm from '~/notes/components/multiline_comment_form.vue';
|
import MultilineCommentForm from '~/notes/components/multiline_comment_form.vue';
|
||||||
import notesModule from '~/notes/stores/modules';
|
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||||
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
|
||||||
|
Vue.use(PiniaVuePlugin);
|
||||||
|
|
||||||
describe('MultilineCommentForm', () => {
|
describe('MultilineCommentForm', () => {
|
||||||
Vue.use(Vuex);
|
let wrapper;
|
||||||
const setSelectedCommentPosition = jest.fn();
|
let pinia;
|
||||||
|
|
||||||
const testLine = {
|
const testLine = {
|
||||||
line_code: 'test',
|
line_code: 'test',
|
||||||
type: 'test',
|
type: 'test',
|
||||||
|
@ -16,63 +21,64 @@ describe('MultilineCommentForm', () => {
|
||||||
new_line: 'test',
|
new_line: 'test',
|
||||||
};
|
};
|
||||||
|
|
||||||
const createWrapper = (props = {}, state) => {
|
const createWrapper = (props = {}) => {
|
||||||
setSelectedCommentPosition.mockReset();
|
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
|
||||||
modules: { notes: notesModule() },
|
|
||||||
actions: { setSelectedCommentPosition },
|
|
||||||
});
|
|
||||||
if (state) store.replaceState({ ...store.state, ...state });
|
|
||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
line: { ...testLine },
|
line: { ...testLine },
|
||||||
commentLineOptions: [{ text: '1' }],
|
commentLineOptions: [{ text: '1' }],
|
||||||
...props,
|
...props,
|
||||||
};
|
};
|
||||||
return mount(MultilineCommentForm, { propsData, store });
|
wrapper = shallowMount(MultilineCommentForm, { propsData, pinia, stubs: { GlSprintf } });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
||||||
|
useLegacyDiffs();
|
||||||
|
useNotes();
|
||||||
|
});
|
||||||
|
|
||||||
describe('created', () => {
|
describe('created', () => {
|
||||||
it('sets commentLineStart to line', () => {
|
it('sets commentLineStart to line', () => {
|
||||||
const line = { ...testLine };
|
const line = { ...testLine };
|
||||||
const wrapper = createWrapper({ line });
|
createWrapper({ line });
|
||||||
|
|
||||||
expect(wrapper.vm.commentLineStart).toEqual(line);
|
// we can't check for .attributes() because of GlFormSelect design
|
||||||
expect(setSelectedCommentPosition).toHaveBeenCalled();
|
// all the attributes get converted to a string, so the line object becomes [object Object]
|
||||||
|
// we can test for the component internals instead which is as reliable as VTUs checks
|
||||||
|
expect(wrapper.findComponent(GlFormSelect).vm.$attrs.value).toEqual(line);
|
||||||
|
expect(useNotes().setSelectedCommentPosition).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets commentLineStart to lineRange', () => {
|
it('sets commentLineStart to lineRange', () => {
|
||||||
const lineRange = {
|
const lineRange = {
|
||||||
start: { ...testLine },
|
start: { ...testLine },
|
||||||
};
|
};
|
||||||
const wrapper = createWrapper({ lineRange });
|
createWrapper({ lineRange });
|
||||||
|
|
||||||
expect(wrapper.vm.commentLineStart).toEqual(lineRange.start);
|
expect(wrapper.findComponent(GlFormSelect).vm.$attrs.value).toEqual(lineRange.start);
|
||||||
expect(setSelectedCommentPosition).toHaveBeenCalled();
|
expect(useNotes().setSelectedCommentPosition).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('destroyed', () => {
|
describe('destroyed', () => {
|
||||||
it('calls setSelectedCommentPosition', () => {
|
it('calls setSelectedCommentPosition', () => {
|
||||||
const wrapper = createWrapper();
|
createWrapper();
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
|
|
||||||
// Once during created, once during destroyed
|
// Once during created, once during destroyed
|
||||||
expect(setSelectedCommentPosition).toHaveBeenCalledTimes(2);
|
expect(useNotes().setSelectedCommentPosition).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles changing the start line', () => {
|
it('handles changing the start line', () => {
|
||||||
const line = { ...testLine };
|
const line = { ...testLine };
|
||||||
const wrapper = createWrapper({ line });
|
createWrapper({ line });
|
||||||
const glSelect = wrapper.findComponent(GlFormSelect);
|
const glSelect = wrapper.findComponent(GlFormSelect);
|
||||||
|
|
||||||
glSelect.vm.$emit('change', { ...testLine });
|
glSelect.vm.$emit('change', { ...testLine });
|
||||||
|
|
||||||
expect(wrapper.vm.commentLineStart).toEqual(line);
|
expect(wrapper.findComponent(GlFormSelect).vm.$attrs.value).toEqual(line);
|
||||||
expect(wrapper.emitted('input')).toHaveLength(1);
|
expect(wrapper.emitted('input')).toHaveLength(1);
|
||||||
// Once during created, once during updateCommentLineStart
|
// Once during created, once during updateCommentLineStart
|
||||||
expect(setSelectedCommentPosition).toHaveBeenCalledTimes(2);
|
expect(useNotes().setSelectedCommentPosition).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,9 @@ import {
|
||||||
} from '@gitlab/ui';
|
} from '@gitlab/ui';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||||
import { nextTick } from 'vue';
|
import Vue, { nextTick } from 'vue';
|
||||||
|
import { PiniaVuePlugin } from 'pinia';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import { stubComponent } from 'helpers/stub_component';
|
import { stubComponent } from 'helpers/stub_component';
|
||||||
import { TEST_HOST } from 'spec/test_constants';
|
import { TEST_HOST } from 'spec/test_constants';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
@ -13,13 +15,17 @@ import noteActions from '~/notes/components/note_actions.vue';
|
||||||
import { NOTEABLE_TYPE_MAPPING } from '~/notes/constants';
|
import { NOTEABLE_TYPE_MAPPING } from '~/notes/constants';
|
||||||
import TimelineEventButton from '~/notes/components/note_actions/timeline_event_button.vue';
|
import TimelineEventButton from '~/notes/components/note_actions/timeline_event_button.vue';
|
||||||
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
|
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
|
||||||
import createStore from '~/notes/stores';
|
|
||||||
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
|
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
|
||||||
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||||
import { userDataMock } from '../mock_data';
|
import { userDataMock } from '../mock_data';
|
||||||
|
|
||||||
|
Vue.use(PiniaVuePlugin);
|
||||||
|
|
||||||
describe('noteActions', () => {
|
describe('noteActions', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let store;
|
let pinia;
|
||||||
let props;
|
let props;
|
||||||
let actions;
|
let actions;
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
|
@ -37,20 +43,20 @@ describe('noteActions', () => {
|
||||||
noteableType,
|
noteableType,
|
||||||
isPromotionInProgress = true,
|
isPromotionInProgress = true,
|
||||||
}) => {
|
}) => {
|
||||||
store.dispatch('setUserData', {
|
useNotes().setUserData({
|
||||||
...userDataMock,
|
...userDataMock,
|
||||||
can_add_timeline_events: userCanAdd,
|
can_add_timeline_events: userCanAdd,
|
||||||
});
|
});
|
||||||
store.state.noteableData = {
|
useNotes().noteableData = {
|
||||||
...store.state.noteableData,
|
...useNotes().noteableData,
|
||||||
type: noteableType,
|
type: noteableType,
|
||||||
};
|
};
|
||||||
store.state.isPromoteCommentToTimelineEventInProgress = isPromotionInProgress;
|
useNotes().isPromoteCommentToTimelineEventInProgress = isPromotionInProgress;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mountNoteActions = (propsData) => {
|
const mountNoteActions = (propsData) => {
|
||||||
return shallowMount(noteActions, {
|
return shallowMount(noteActions, {
|
||||||
store,
|
pinia,
|
||||||
propsData,
|
propsData,
|
||||||
stubs: {
|
stubs: {
|
||||||
GlDisclosureDropdown: stubComponent(GlDisclosureDropdown, {
|
GlDisclosureDropdown: stubComponent(GlDisclosureDropdown, {
|
||||||
|
@ -65,7 +71,10 @@ describe('noteActions', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store = createStore();
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin], stubActions: false });
|
||||||
|
useLegacyDiffs();
|
||||||
|
useNotes().toggleAwardRequest.mockResolvedValue();
|
||||||
|
useNotes().promoteCommentToTimelineEvent.mockResolvedValue();
|
||||||
|
|
||||||
props = {
|
props = {
|
||||||
accessLevel: 'Maintainer',
|
accessLevel: 'Maintainer',
|
||||||
|
@ -98,7 +107,7 @@ describe('noteActions', () => {
|
||||||
|
|
||||||
describe('user is logged in', () => {
|
describe('user is logged in', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.dispatch('setUserData', userDataMock);
|
useNotes().setUserData(userDataMock);
|
||||||
|
|
||||||
wrapper = mountNoteActions(props);
|
wrapper = mountNoteActions(props);
|
||||||
});
|
});
|
||||||
|
@ -222,13 +231,13 @@ describe('noteActions', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = mountNoteActions(props);
|
wrapper = mountNoteActions(props);
|
||||||
store.state.noteableData = {
|
useNotes().noteableData = {
|
||||||
current_user: {
|
current_user: {
|
||||||
can_set_issue_metadata: true,
|
can_set_issue_metadata: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
store.state.userData = userDataMock;
|
useNotes().userData = userDataMock;
|
||||||
store.state.noteableData.targetType = 'issue';
|
useNotes().noteableData.targetType = 'issue';
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -244,13 +253,13 @@ describe('noteActions', () => {
|
||||||
wrapper = mountNoteActions(props, {
|
wrapper = mountNoteActions(props, {
|
||||||
targetType: () => 'issue',
|
targetType: () => 'issue',
|
||||||
});
|
});
|
||||||
store.state.noteableData = {
|
useNotes().noteableData = {
|
||||||
current_user: {
|
current_user: {
|
||||||
can_update: true,
|
can_update: true,
|
||||||
can_set_issue_metadata: false,
|
can_set_issue_metadata: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
store.state.userData = userDataMock;
|
useNotes().userData = userDataMock;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -282,7 +291,7 @@ describe('noteActions', () => {
|
||||||
describe('user is not logged in', () => {
|
describe('user is not logged in', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// userData can be null https://gitlab.com/gitlab-org/gitlab/-/issues/379375
|
// userData can be null https://gitlab.com/gitlab-org/gitlab/-/issues/379375
|
||||||
store.dispatch('setUserData', null);
|
useNotes().setUserData(null);
|
||||||
wrapper = mountNoteActions({
|
wrapper = mountNoteActions({
|
||||||
...props,
|
...props,
|
||||||
canDelete: false,
|
canDelete: false,
|
||||||
|
@ -333,7 +342,7 @@ describe('noteActions', () => {
|
||||||
|
|
||||||
describe('Draft notes', () => {
|
describe('Draft notes', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.dispatch('setUserData', userDataMock);
|
useNotes().setUserData(userDataMock);
|
||||||
|
|
||||||
wrapper = mountNoteActions({ ...props, canResolve: true, isDraft: true });
|
wrapper = mountNoteActions({ ...props, canResolve: true, isDraft: true });
|
||||||
});
|
});
|
||||||
|
@ -386,14 +395,11 @@ describe('noteActions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when timeline-event-button emits click-promote-comment-to-event, dispatches action', () => {
|
it('when timeline-event-button emits click-promote-comment-to-event, dispatches action', () => {
|
||||||
jest.spyOn(store, 'dispatch').mockImplementation();
|
expect(useNotes().promoteCommentToTimelineEvent).not.toHaveBeenCalled();
|
||||||
|
|
||||||
expect(store.dispatch).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
findTimelineButton().vm.$emit('click-promote-comment-to-event');
|
findTimelineButton().vm.$emit('click-promote-comment-to-event');
|
||||||
|
|
||||||
expect(store.dispatch).toHaveBeenCalledTimes(1);
|
expect(useNotes().promoteCommentToTimelineEvent).toHaveBeenCalledTimes(1);
|
||||||
expect(store.dispatch).toHaveBeenCalledWith('promoteCommentToTimelineEvent');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -403,7 +409,7 @@ describe('noteActions', () => {
|
||||||
|
|
||||||
describe('when user is not allowed to report abuse', () => {
|
describe('when user is not allowed to report abuse', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.dispatch('setUserData', userDataMock);
|
useNotes().setUserData(userDataMock);
|
||||||
wrapper = mountNoteActions({ ...props, canReportAsAbuse: false });
|
wrapper = mountNoteActions({ ...props, canReportAsAbuse: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -418,7 +424,7 @@ describe('noteActions', () => {
|
||||||
|
|
||||||
describe('when user is allowed to report abuse', () => {
|
describe('when user is allowed to report abuse', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.dispatch('setUserData', userDataMock);
|
useNotes().setUserData(userDataMock);
|
||||||
wrapper = mountNoteActions({ ...props, canReportAsAbuse: true });
|
wrapper = mountNoteActions({ ...props, canReportAsAbuse: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { PiniaVuePlugin } from 'pinia';
|
||||||
import Vuex from 'vuex';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import { TEST_HOST } from 'helpers/test_constants';
|
import { TEST_HOST } from 'helpers/test_constants';
|
||||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import { userDataMock } from 'jest/notes/mock_data';
|
import { userDataMock } from 'jest/notes/mock_data';
|
||||||
|
@ -9,12 +9,15 @@ import EmojiPicker from '~/emoji/components/picker.vue';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||||
import awardsNote from '~/notes/components/note_awards_list.vue';
|
import awardsNote from '~/notes/components/note_awards_list.vue';
|
||||||
import createStore from '~/notes/stores';
|
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||||
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(PiniaVuePlugin);
|
||||||
|
|
||||||
describe('Note Awards List', () => {
|
describe('Note Awards List', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let pinia;
|
||||||
let mock;
|
let mock;
|
||||||
|
|
||||||
const awardsMock = [
|
const awardsMock = [
|
||||||
|
@ -42,42 +45,34 @@ describe('Note Awards List', () => {
|
||||||
const findAllEmojiAwards = () => wrapper.findAll('gl-emoji');
|
const findAllEmojiAwards = () => wrapper.findAll('gl-emoji');
|
||||||
const findEmojiPicker = () => wrapper.findComponent(EmojiPicker);
|
const findEmojiPicker = () => wrapper.findComponent(EmojiPicker);
|
||||||
|
|
||||||
const createComponent = (props = defaultProps, store = createStore()) => {
|
const createComponent = (props = defaultProps) => {
|
||||||
wrapper = mountExtended(awardsNote, {
|
wrapper = mountExtended(awardsNote, {
|
||||||
store,
|
pinia,
|
||||||
propsData: {
|
propsData: {
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Note Awards functionality', () => {
|
beforeEach(() => {
|
||||||
const toggleAwardRequestSpy = jest.fn();
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
||||||
const fakeStore = () => {
|
useLegacyDiffs();
|
||||||
return new Vuex.Store({
|
useNotes().setUserData(userDataMock);
|
||||||
getters: {
|
useNotes().toggleAwardRequest.mockResolvedValue();
|
||||||
getUserData: () => userDataMock,
|
});
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
toggleAwardRequest: toggleAwardRequestSpy,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
describe('Note Awards functionality', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mock = new AxiosMockAdapter(axios);
|
mock = new AxiosMockAdapter(axios);
|
||||||
mock.onPost(toggleAwardPathMock).reply(HTTP_STATUS_OK, '');
|
mock.onPost(toggleAwardPathMock).reply(HTTP_STATUS_OK, '');
|
||||||
|
|
||||||
createComponent(
|
createComponent({
|
||||||
{
|
awards: awardsMock,
|
||||||
awards: awardsMock,
|
noteAuthorId: 2,
|
||||||
noteAuthorId: 2,
|
noteId: '545',
|
||||||
noteId: '545',
|
canAwardEmoji: true,
|
||||||
canAwardEmoji: true,
|
toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji',
|
||||||
toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji',
|
});
|
||||||
},
|
|
||||||
fakeStore(),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -100,7 +95,7 @@ describe('Note Awards List', () => {
|
||||||
await findAwardButton().vm.$emit('click');
|
await findAwardButton().vm.$emit('click');
|
||||||
|
|
||||||
const { toggleAwardPath, noteId } = defaultProps;
|
const { toggleAwardPath, noteId } = defaultProps;
|
||||||
expect(toggleAwardRequestSpy).toHaveBeenCalledWith(expect.anything(), {
|
expect(useNotes().toggleAwardRequest).toHaveBeenCalledWith({
|
||||||
awardName: awardsMock[0].name,
|
awardName: awardsMock[0].name,
|
||||||
endpoint: toggleAwardPath,
|
endpoint: toggleAwardPath,
|
||||||
noteId,
|
noteId,
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import Vuex from 'vuex';
|
|
||||||
import { PiniaVuePlugin } from 'pinia';
|
import { PiniaVuePlugin } from 'pinia';
|
||||||
import { createTestingPinia } from '@pinia/testing';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import NoteBody from '~/notes/components/note_body.vue';
|
import NoteBody from '~/notes/components/note_body.vue';
|
||||||
import NoteAwardsList from '~/notes/components/note_awards_list.vue';
|
import NoteAwardsList from '~/notes/components/note_awards_list.vue';
|
||||||
import NoteForm from '~/notes/components/note_form.vue';
|
import NoteForm from '~/notes/components/note_form.vue';
|
||||||
import createStore from '~/notes/stores';
|
|
||||||
import notes from '~/notes/stores/modules/index';
|
|
||||||
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
|
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
|
||||||
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
import { globalAccessorPlugin } from '~/pinia/plugins';
|
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||||
|
@ -24,23 +20,8 @@ describe('issue_note_body component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let pinia;
|
let pinia;
|
||||||
|
|
||||||
const createComponent = ({
|
const createComponent = (props = {}) => {
|
||||||
props = {},
|
|
||||||
noteableData = noteableDataMock,
|
|
||||||
notesData = notesDataMock,
|
|
||||||
store = null,
|
|
||||||
} = {}) => {
|
|
||||||
let mockStore;
|
|
||||||
|
|
||||||
if (!store) {
|
|
||||||
mockStore = createStore();
|
|
||||||
|
|
||||||
mockStore.dispatch('setNoteableData', noteableData);
|
|
||||||
mockStore.dispatch('setNotesData', notesData);
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper = shallowMountExtended(NoteBody, {
|
wrapper = shallowMountExtended(NoteBody, {
|
||||||
store: mockStore || store,
|
|
||||||
pinia,
|
pinia,
|
||||||
propsData: {
|
propsData: {
|
||||||
note,
|
note,
|
||||||
|
@ -58,7 +39,8 @@ describe('issue_note_body component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
||||||
useLegacyDiffs();
|
useLegacyDiffs();
|
||||||
useNotes();
|
useNotes().setNoteableData(noteableDataMock);
|
||||||
|
useNotes().setNotesData(notesDataMock);
|
||||||
useMrNotes();
|
useMrNotes();
|
||||||
createComponent();
|
createComponent();
|
||||||
});
|
});
|
||||||
|
@ -73,7 +55,7 @@ describe('issue_note_body component', () => {
|
||||||
|
|
||||||
describe('isInternalNote', () => {
|
describe('isInternalNote', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createComponent({ props: { isInternalNote: true } });
|
createComponent({ isInternalNote: true });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -82,7 +64,9 @@ describe('issue_note_body component', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createComponent({
|
createComponent({
|
||||||
props: { isEditing: true, autosaveKey, restoreFromAutosave: true },
|
isEditing: true,
|
||||||
|
autosaveKey,
|
||||||
|
restoreFromAutosave: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -98,7 +82,7 @@ describe('issue_note_body component', () => {
|
||||||
${false} | ${'Save comment'}
|
${false} | ${'Save comment'}
|
||||||
${true} | ${'Save internal note'}
|
${true} | ${'Save internal note'}
|
||||||
`('renders save button with text "$buttonText"', ({ internal, buttonText }) => {
|
`('renders save button with text "$buttonText"', ({ internal, buttonText }) => {
|
||||||
createComponent({ props: { note: { ...note, internal }, isEditing: true } });
|
createComponent({ note: { ...note, internal }, isEditing: true });
|
||||||
|
|
||||||
expect(wrapper.findComponent(NoteForm).props('saveButtonTitle')).toBe(buttonText);
|
expect(wrapper.findComponent(NoteForm).props('saveButtonTitle')).toBe(buttonText);
|
||||||
});
|
});
|
||||||
|
@ -112,40 +96,20 @@ describe('issue_note_body component', () => {
|
||||||
|
|
||||||
describe('commitMessage', () => {
|
describe('commitMessage', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const mrMetadata = {
|
useMrNotes().mrMetadata = {
|
||||||
branch_name: 'branch',
|
branch_name: 'branch',
|
||||||
project_path: '/path',
|
project_path: '/path',
|
||||||
project_name: 'name',
|
project_name: 'name',
|
||||||
username: 'user',
|
username: 'user',
|
||||||
user_full_name: 'user userton',
|
user_full_name: 'user userton',
|
||||||
};
|
};
|
||||||
const notesStore = notes();
|
|
||||||
|
|
||||||
notesStore.state.notes = {};
|
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
|
||||||
modules: {
|
|
||||||
notes: notesStore,
|
|
||||||
page: {
|
|
||||||
namespaced: true,
|
|
||||||
state: {
|
|
||||||
mrMetadata,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useMrNotes().mrMetadata = mrMetadata;
|
|
||||||
useLegacyDiffs().defaultSuggestionCommitMessage =
|
useLegacyDiffs().defaultSuggestionCommitMessage =
|
||||||
'*** %{branch_name} %{project_path} %{project_name} %{username} %{user_full_name} %{file_paths} %{suggestions_count} %{files_count} %{co_authored_by}';
|
'*** %{branch_name} %{project_path} %{project_name} %{username} %{user_full_name} %{file_paths} %{suggestions_count} %{files_count} %{co_authored_by}';
|
||||||
|
|
||||||
createComponent({
|
createComponent({
|
||||||
store,
|
note: { ...note, suggestions: [12345] },
|
||||||
props: {
|
canEdit: true,
|
||||||
note: { ...note, suggestions: [12345] },
|
file: { file_path: 'abc' },
|
||||||
canEdit: true,
|
|
||||||
file: { file_path: 'abc' },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -159,19 +123,6 @@ describe('issue_note_body component', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('duo code review feedback', () => {
|
describe('duo code review feedback', () => {
|
||||||
const createMockStoreWithDiscussion = (discussionId, discussionNotes) => {
|
|
||||||
return new Vuex.Store({
|
|
||||||
getters: {
|
|
||||||
getDiscussion: () => (id) => {
|
|
||||||
if (id === discussionId) {
|
|
||||||
return { notes: discussionNotes };
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
it.each`
|
it.each`
|
||||||
userType | type | exists | existsText
|
userType | type | exists | existsText
|
||||||
${'duo_code_review_bot'} | ${null} | ${true} | ${'renders'}
|
${'duo_code_review_bot'} | ${null} | ${true} | ${'renders'}
|
||||||
|
@ -191,12 +142,9 @@ describe('issue_note_body component', () => {
|
||||||
user_type: userType,
|
user_type: userType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const mockStore = createMockStoreWithDiscussion('discussion1', [duoNote]);
|
useNotes().discussions = [{ id: 'discussion1', notes: [duoNote] }];
|
||||||
|
|
||||||
createComponent({
|
createComponent({ note: duoNote });
|
||||||
props: { note: duoNote },
|
|
||||||
store: mockStore,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.findByTestId('code-review-feedback').exists()).toBe(exists);
|
expect(wrapper.findByTestId('code-review-feedback').exists()).toBe(exists);
|
||||||
},
|
},
|
||||||
|
@ -213,41 +161,15 @@ describe('issue_note_body component', () => {
|
||||||
user_type: 'duo_code_review_bot',
|
user_type: 'duo_code_review_bot',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const mockStore = createMockStoreWithDiscussion('discussion1', [note, duoNote]);
|
useNotes().discussions = [{ id: 'discussion1', notes: [note, duoNote] }];
|
||||||
|
|
||||||
createComponent({
|
createComponent({ note: duoNote });
|
||||||
props: { note: duoNote },
|
|
||||||
store: mockStore,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.findByTestId('code-review-feedback').exists()).toBe(false);
|
expect(wrapper.findByTestId('code-review-feedback').exists()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('duo code review feedback text', () => {
|
describe('duo code review feedback text', () => {
|
||||||
const createMockStoreWithDiscussion = (discussionId, discussionNotes) => {
|
|
||||||
return new Vuex.Store({
|
|
||||||
getters: {
|
|
||||||
getDiscussion: () => (id) => {
|
|
||||||
if (id === discussionId) {
|
|
||||||
return { notes: discussionNotes };
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
suggestionsCount: () => 0,
|
|
||||||
getSuggestionsFilePaths: () => [],
|
|
||||||
},
|
|
||||||
modules: {
|
|
||||||
notes: {
|
|
||||||
state: { batchSuggestionsInfo: [] },
|
|
||||||
},
|
|
||||||
page: {
|
|
||||||
state: { failedToLoadMetadata: false },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const createDuoNote = (props = {}) => ({
|
const createDuoNote = (props = {}) => ({
|
||||||
...note,
|
...note,
|
||||||
id: '1',
|
id: '1',
|
||||||
|
@ -262,11 +184,10 @@ describe('issue_note_body component', () => {
|
||||||
|
|
||||||
it('renders feedback text for the first DiffNote from GitLabDuo', () => {
|
it('renders feedback text for the first DiffNote from GitLabDuo', () => {
|
||||||
const duoNote = createDuoNote();
|
const duoNote = createDuoNote();
|
||||||
const mockStore = createMockStoreWithDiscussion('discussion1', [duoNote]);
|
useNotes().discussions = [{ id: 'discussion1', notes: [duoNote] }];
|
||||||
|
|
||||||
createComponent({
|
createComponent({
|
||||||
props: { note: duoNote },
|
note: duoNote,
|
||||||
store: mockStore,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const feedbackDiv = wrapper.find('.gl-text-md.gl-mt-4.gl-text-gray-500');
|
const feedbackDiv = wrapper.find('.gl-text-md.gl-mt-4.gl-text-gray-500');
|
||||||
|
@ -275,9 +196,10 @@ describe('issue_note_body component', () => {
|
||||||
|
|
||||||
it('does not render feedback text for non-DiffNote from GitLabDuo', () => {
|
it('does not render feedback text for non-DiffNote from GitLabDuo', () => {
|
||||||
const duoNote = createDuoNote({ type: 'DiscussionNote' });
|
const duoNote = createDuoNote({ type: 'DiscussionNote' });
|
||||||
|
useNotes().discussions = [{ id: 'discussion1', notes: [duoNote] }];
|
||||||
|
|
||||||
createComponent({
|
createComponent({
|
||||||
props: { note: duoNote },
|
note: duoNote,
|
||||||
});
|
});
|
||||||
|
|
||||||
const feedbackDiv = wrapper.find('.gl-text-md.gl-mt-4.gl-text-gray-500');
|
const feedbackDiv = wrapper.find('.gl-text-md.gl-mt-4.gl-text-gray-500');
|
||||||
|
@ -286,14 +208,10 @@ describe('issue_note_body component', () => {
|
||||||
|
|
||||||
it('does not render feedback text for follow-up DiffNote from GitLabDuo', () => {
|
it('does not render feedback text for follow-up DiffNote from GitLabDuo', () => {
|
||||||
const duoNote = createDuoNote({ id: '2' });
|
const duoNote = createDuoNote({ id: '2' });
|
||||||
const mockStore = createMockStoreWithDiscussion('discussion1', [
|
useNotes().discussions = [{ id: 'discussion1', notes: [{ id: '1' }, duoNote] }];
|
||||||
{ id: '1' }, // First note has different ID
|
|
||||||
duoNote,
|
|
||||||
]);
|
|
||||||
|
|
||||||
createComponent({
|
createComponent({
|
||||||
props: { note: duoNote },
|
note: duoNote,
|
||||||
store: mockStore,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const feedbackDiv = wrapper.find('.gl-text-md.gl-mt-4.gl-text-gray-500');
|
const feedbackDiv = wrapper.find('.gl-text-md.gl-mt-4.gl-text-gray-500');
|
||||||
|
@ -302,11 +220,10 @@ describe('issue_note_body component', () => {
|
||||||
|
|
||||||
it('shows default awards list with thumbsup and thumbsdown for first DiffNote from GitLabDuo', () => {
|
it('shows default awards list with thumbsup and thumbsdown for first DiffNote from GitLabDuo', () => {
|
||||||
const duoNote = createDuoNote();
|
const duoNote = createDuoNote();
|
||||||
const mockStore = createMockStoreWithDiscussion('discussion1', [duoNote]);
|
useNotes().discussions = [{ id: 'discussion1', notes: [duoNote] }];
|
||||||
|
|
||||||
createComponent({
|
createComponent({
|
||||||
props: { note: duoNote },
|
note: duoNote,
|
||||||
store: mockStore,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const awardsList = wrapper.findComponent(NoteAwardsList);
|
const awardsList = wrapper.findComponent(NoteAwardsList);
|
||||||
|
@ -325,7 +242,7 @@ describe('issue_note_body component', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
createComponent({
|
createComponent({
|
||||||
props: { note: regularNote },
|
note: regularNote,
|
||||||
});
|
});
|
||||||
|
|
||||||
const awardsList = wrapper.findComponent(NoteAwardsList);
|
const awardsList = wrapper.findComponent(NoteAwardsList);
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import Vue, { nextTick } from 'vue';
|
import Vue, { nextTick } from 'vue';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
import { PiniaVuePlugin } from 'pinia';
|
||||||
import Vuex from 'vuex';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
import NoteHeader from '~/notes/components/note_header.vue';
|
import NoteHeader from '~/notes/components/note_header.vue';
|
||||||
import ImportedBadge from '~/vue_shared/components/imported_badge.vue';
|
import ImportedBadge from '~/vue_shared/components/imported_badge.vue';
|
||||||
|
import { globalAccessorPlugin } from '~/pinia/plugins';
|
||||||
|
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
|
||||||
|
import { useNotes } from '~/notes/store/legacy_notes';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(PiniaVuePlugin);
|
||||||
|
|
||||||
const actions = {
|
|
||||||
setTargetNoteHash: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('NoteHeader component', () => {
|
describe('NoteHeader component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let pinia;
|
||||||
|
|
||||||
const findActionText = () => wrapper.findComponent({ ref: 'actionText' });
|
const findActionText = () => wrapper.findComponent({ ref: 'actionText' });
|
||||||
const findTimestampLink = () => wrapper.findComponent({ ref: 'noteTimestampLink' });
|
const findTimestampLink = () => wrapper.findComponent({ ref: 'noteTimestampLink' });
|
||||||
|
@ -51,13 +52,17 @@ describe('NoteHeader component', () => {
|
||||||
|
|
||||||
const createComponent = (props) => {
|
const createComponent = (props) => {
|
||||||
wrapper = shallowMountExtended(NoteHeader, {
|
wrapper = shallowMountExtended(NoteHeader, {
|
||||||
store: new Vuex.Store({
|
pinia,
|
||||||
actions,
|
|
||||||
}),
|
|
||||||
propsData: { ...props },
|
propsData: { ...props },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
||||||
|
useLegacyDiffs();
|
||||||
|
useNotes();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders an author link if author is passed to props', () => {
|
it('renders an author link if author is passed to props', () => {
|
||||||
createComponent({ author });
|
createComponent({ author });
|
||||||
|
|
||||||
|
@ -106,14 +111,16 @@ describe('NoteHeader component', () => {
|
||||||
expect(findActionText().text()).toBe('Test action text');
|
expect(findActionText().text()).toBe('Test action text');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls an action when timestamp is clicked', () => {
|
it('calls an action when timestamp is clicked', async () => {
|
||||||
createComponent({
|
createComponent({
|
||||||
createdAt: '2017-08-02T10:51:58.559Z',
|
createdAt: '2017-08-02T10:51:58.559Z',
|
||||||
noteId: 123,
|
noteId: 123,
|
||||||
});
|
});
|
||||||
findTimestampLink().vm.$emit('click');
|
findTimestampLink().vm.$emit('click');
|
||||||
|
|
||||||
expect(actions.setTargetNoteHash).toHaveBeenCalled();
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(useNotes().setTargetNoteHash).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,8 @@ describe('noteable_discussion component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
pinia = createTestingPinia({ plugins: [globalAccessorPlugin] });
|
||||||
useLegacyDiffs();
|
useLegacyDiffs();
|
||||||
useNotes();
|
useNotes().saveNote.mockResolvedValue();
|
||||||
|
useNotes().fetchDiscussionDiffLines.mockResolvedValue();
|
||||||
useBatchComments();
|
useBatchComments();
|
||||||
axiosMock = new MockAdapter(axios);
|
axiosMock = new MockAdapter(axios);
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { nextTick } from 'vue';
|
|
||||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import TagsHeader from '~/packages_and_registries/harbor_registry/components/tags/tags_header.vue';
|
import TagsHeader from '~/packages_and_registries/harbor_registry/components/tags/tags_header.vue';
|
||||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||||
|
import { EMPTY_TAG_LABEL } from '~/packages_and_registries/harbor_registry/constants';
|
||||||
import { mockArtifactDetail, MOCK_SHA_DIGEST } from '../../mock_data';
|
import { mockArtifactDetail, MOCK_SHA_DIGEST } from '../../mock_data';
|
||||||
|
|
||||||
describe('Harbor Tags Header', () => {
|
describe('Harbor Tags Header', () => {
|
||||||
|
@ -28,7 +28,11 @@ describe('Harbor Tags Header', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mountComponent({
|
mountComponent({
|
||||||
propsData: { artifactDetail: mockArtifactDetail, pageInfo: mockPageInfo, tagsLoading: false },
|
propsData: {
|
||||||
|
artifactDetail: mockArtifactDetail,
|
||||||
|
pageInfo: mockPageInfo,
|
||||||
|
tagsLoading: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -39,10 +43,54 @@ describe('Harbor Tags Header', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('tags count', () => {
|
describe('tags count', () => {
|
||||||
it('would has the correct text', async () => {
|
it('displays the tags count', () => {
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(findTagsCount().props('text')).toBe('1 tag');
|
expect(findTagsCount().props('text')).toBe('1 tag');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when pageInfo.total is NaN', () => {
|
||||||
|
const nanMockPageInfo = {
|
||||||
|
page: 1,
|
||||||
|
perPage: 20,
|
||||||
|
total: NaN,
|
||||||
|
totalPages: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mountComponent({
|
||||||
|
propsData: {
|
||||||
|
artifactDetail: mockArtifactDetail,
|
||||||
|
pageInfo: nanMockPageInfo,
|
||||||
|
tagsLoading: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays empty label when there are no tags', () => {
|
||||||
|
expect(findTagsCount().props('text')).toBe(EMPTY_TAG_LABEL);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when pageInfo.total is 0', () => {
|
||||||
|
const nanMockPageInfo = {
|
||||||
|
page: 1,
|
||||||
|
perPage: 20,
|
||||||
|
total: 0,
|
||||||
|
totalPages: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mountComponent({
|
||||||
|
propsData: {
|
||||||
|
artifactDetail: mockArtifactDetail,
|
||||||
|
pageInfo: nanMockPageInfo,
|
||||||
|
tagsLoading: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays empty label when there are no tags', () => {
|
||||||
|
expect(findTagsCount().props('text')).toBe(EMPTY_TAG_LABEL);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ import axios from '~/lib/utils/axios_utils';
|
||||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||||
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||||
import * as urlUtils from '~/lib/utils/url_utility';
|
import * as urlUtils from '~/lib/utils/url_utility';
|
||||||
import * as actions from '~/search/store/actions';
|
|
||||||
import {
|
import {
|
||||||
GROUPS_LOCAL_STORAGE_KEY,
|
GROUPS_LOCAL_STORAGE_KEY,
|
||||||
PROJECTS_LOCAL_STORAGE_KEY,
|
PROJECTS_LOCAL_STORAGE_KEY,
|
||||||
|
@ -22,6 +22,7 @@ import {
|
||||||
import * as types from '~/search/store/mutation_types';
|
import * as types from '~/search/store/mutation_types';
|
||||||
import createState from '~/search/store/state';
|
import createState from '~/search/store/state';
|
||||||
import * as storeUtils from '~/search/store/utils';
|
import * as storeUtils from '~/search/store/utils';
|
||||||
|
import * as actions from '~/search/store/actions';
|
||||||
import {
|
import {
|
||||||
MOCK_QUERY,
|
MOCK_QUERY,
|
||||||
MOCK_GROUPS,
|
MOCK_GROUPS,
|
||||||
|
@ -203,26 +204,12 @@ describe('Global Search Store Actions', () => {
|
||||||
fetchSidebarCountSpy.mockRestore();
|
fetchSidebarCountSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update URL, document title, and history', async () => {
|
it('should update URL, document title, and history', () => {
|
||||||
const getters = { currentScope: 'blobs' };
|
const getters = { currentScope: 'blobs' };
|
||||||
|
|
||||||
await actions.setQuery({ state, commit, getters }, payload);
|
return testAction(actions.setQuery, payload, { ...state, ...getters }, [
|
||||||
|
{ type: types.SET_QUERY, payload: { key: 'some-key', value: 'some-value' } },
|
||||||
expect(setUrlParams).toHaveBeenCalledWith(
|
]);
|
||||||
{ ...state.query },
|
|
||||||
window.location.href,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(document.title).toBe(state.query.search);
|
|
||||||
|
|
||||||
expect(updateHistory).toHaveBeenCalledWith({
|
|
||||||
state: state.query,
|
|
||||||
title: state.query.search,
|
|
||||||
url: 'mocked-new-url',
|
|
||||||
replace: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not update URL or fetch sidebar counts when conditions are not met', async () => {
|
it('does not update URL or fetch sidebar counts when conditions are not met', async () => {
|
||||||
|
@ -268,6 +255,156 @@ describe('Global Search Store Actions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('zoekt search type with blob scope - page handling scenarios', () => {
|
||||||
|
let originalGon;
|
||||||
|
let commit;
|
||||||
|
let fetchSidebarCountSpy;
|
||||||
|
let modifySearchQuerySpy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalGon = window.gon;
|
||||||
|
commit = jest.fn();
|
||||||
|
|
||||||
|
fetchSidebarCountSpy = jest
|
||||||
|
.spyOn(actions, 'fetchSidebarCount')
|
||||||
|
.mockImplementation(() => Promise.resolve());
|
||||||
|
|
||||||
|
modifySearchQuerySpy = jest
|
||||||
|
.spyOn(storeUtils, 'modifySearchQuery')
|
||||||
|
.mockReturnValue('mocked-clean-url');
|
||||||
|
|
||||||
|
window.gon = { features: {} };
|
||||||
|
storeUtils.isSidebarDirty = jest.fn().mockReturnValue(false);
|
||||||
|
storeUtils.buildDocumentTitle = jest.fn().mockReturnValue('Built Document Title');
|
||||||
|
|
||||||
|
state = createState({
|
||||||
|
query: { ...MOCK_QUERY, search: 'test-search' },
|
||||||
|
navigation: { ...MOCK_NAVIGATION },
|
||||||
|
searchType: 'zoekt',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
window.gon = originalGon;
|
||||||
|
fetchSidebarCountSpy.mockRestore();
|
||||||
|
modifySearchQuerySpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when only "page" attribute changes', () => {
|
||||||
|
it('should only update history without fetching sidebar counts', async () => {
|
||||||
|
const getters = { currentScope: 'blobs' };
|
||||||
|
const payload = { key: 'page', value: 2 };
|
||||||
|
|
||||||
|
await actions.setQuery({ state, commit, getters }, payload);
|
||||||
|
|
||||||
|
expect(setUrlParams).toHaveBeenCalledWith(
|
||||||
|
{ ...state.query },
|
||||||
|
window.location.href,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(updateHistory).toHaveBeenCalledWith({
|
||||||
|
state: state.query,
|
||||||
|
title: state.query.search,
|
||||||
|
url: 'mocked-new-url',
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fetchSidebarCountSpy).not.toHaveBeenCalled();
|
||||||
|
expect(commit).toHaveBeenCalledWith(types.SET_QUERY, payload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when "search" attribute changes and page attribute is not present', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const res = { count: 666 };
|
||||||
|
mock.onGet().replyOnce(HTTP_STATUS_OK, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update URL, title, history and fetch sidebar counts', async () => {
|
||||||
|
const getters = { currentScope: 'blobs' };
|
||||||
|
const payload = { key: 'search', value: 'new-search' };
|
||||||
|
|
||||||
|
state.query = { ...state.query };
|
||||||
|
delete state.query.page;
|
||||||
|
state.urlQuery = { ...state.urlQuery };
|
||||||
|
delete state.urlQuery.page;
|
||||||
|
|
||||||
|
await testAction(
|
||||||
|
actions.setQuery,
|
||||||
|
payload,
|
||||||
|
{ ...state, ...getters },
|
||||||
|
[{ type: types.SET_QUERY, payload: { key: 'search', value: 'new-search' } }],
|
||||||
|
[{ type: 'fetchSidebarCount' }],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when "search" attribute changes but page is present and not equal to 1', () => {
|
||||||
|
it('should reset page to 1, update URL with clean URL, and fetch sidebar counts', () => {
|
||||||
|
const getters = { currentScope: 'blobs' };
|
||||||
|
const payload = { key: 'search', value: 'new-search' };
|
||||||
|
|
||||||
|
state.query = { ...state.query, page: 3 };
|
||||||
|
state.urlQuery = { ...state.urlQuery, page: 3 };
|
||||||
|
|
||||||
|
return testAction(
|
||||||
|
actions.setQuery,
|
||||||
|
payload,
|
||||||
|
{ ...state, ...getters },
|
||||||
|
[
|
||||||
|
{ type: types.SET_QUERY, payload: { key: 'search', value: 'new-search' } },
|
||||||
|
{ type: types.SET_QUERY, payload: { key: 'page', value: 1 } },
|
||||||
|
],
|
||||||
|
[{ type: 'fetchSidebarCount' }],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when "search" attribute changes but page is present and equal to 1', () => {
|
||||||
|
it('should not modify URL for page, update history with original URL', async () => {
|
||||||
|
const getters = { currentScope: 'blobs' };
|
||||||
|
const payload = { key: 'search', value: 'new-search' };
|
||||||
|
|
||||||
|
state.query = { ...state.query, page: 1 };
|
||||||
|
state.urlQuery = { ...state.urlQuery, page: 1 };
|
||||||
|
|
||||||
|
await testAction(
|
||||||
|
actions.setQuery,
|
||||||
|
payload,
|
||||||
|
{ ...state, ...getters },
|
||||||
|
[{ type: types.SET_QUERY, payload: { key: 'search', value: 'new-search' } }],
|
||||||
|
[{ type: 'fetchSidebarCount' }],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(updateHistory).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when urlQuery has no page but state.query has page', () => {
|
||||||
|
it('should use original URL without modification', () => {
|
||||||
|
const getters = { currentScope: 'blobs' };
|
||||||
|
const payload = { key: 'search', value: 'new-search' };
|
||||||
|
|
||||||
|
state.query = { ...state.query, page: 2 };
|
||||||
|
state.urlQuery = { ...state.urlQuery };
|
||||||
|
delete state.urlQuery.page;
|
||||||
|
|
||||||
|
return testAction(
|
||||||
|
actions.setQuery,
|
||||||
|
payload,
|
||||||
|
{ ...state, ...getters },
|
||||||
|
[
|
||||||
|
{ type: types.SET_QUERY, payload: { key: 'search', value: 'new-search' } },
|
||||||
|
{ type: types.SET_QUERY, payload: { key: 'page', value: 1 } },
|
||||||
|
],
|
||||||
|
[{ type: 'fetchSidebarCount' }],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('applyQuery', () => {
|
describe('applyQuery', () => {
|
||||||
|
@ -410,7 +547,7 @@ describe('Global Search Store Actions', () => {
|
||||||
it(`should ${expectedMutations.length === 0 ? 'NOT' : ''} dispatch ${
|
it(`should ${expectedMutations.length === 0 ? 'NOT' : ''} dispatch ${
|
||||||
expectedMutations.length === 0 ? '' : 'the correct'
|
expectedMutations.length === 0 ? '' : 'the correct'
|
||||||
} mutations for ${scope}`, () => {
|
} mutations for ${scope}`, () => {
|
||||||
return testAction({ action, state, expectedMutations }).then(() => {
|
return testAction(action, undefined, state, expectedMutations, []).then(() => {
|
||||||
expect(logger.logError).toHaveBeenCalledTimes(errorLogs);
|
expect(logger.logError).toHaveBeenCalledTimes(errorLogs);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { GlAnimatedChevronLgDownUpIcon } from '@gitlab/ui';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||||
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
|
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
|
@ -11,6 +12,7 @@ import WidgetContentRow from '~/vue_merge_request_widget/components/widget/widge
|
||||||
import ReportListItem from '~/merge_requests/reports/components/report_list_item.vue';
|
import ReportListItem from '~/merge_requests/reports/components/report_list_item.vue';
|
||||||
import * as logger from '~/lib/logger';
|
import * as logger from '~/lib/logger';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||||
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||||
|
|
||||||
jest.mock('~/vue_merge_request_widget/components/widget/telemetry', () => ({
|
jest.mock('~/vue_merge_request_widget/components/widget/telemetry', () => ({
|
||||||
|
@ -28,6 +30,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
|
||||||
const findExpandedSection = () => wrapper.findByTestId('widget-extension-collapsed-section');
|
const findExpandedSection = () => wrapper.findByTestId('widget-extension-collapsed-section');
|
||||||
const findActionButtons = () => wrapper.findComponent(ActionButtons);
|
const findActionButtons = () => wrapper.findComponent(ActionButtons);
|
||||||
const findToggleButton = () => wrapper.findByTestId('toggle-button');
|
const findToggleButton = () => wrapper.findByTestId('toggle-button');
|
||||||
|
const findToggleChevron = () => findToggleButton().findComponent(GlAnimatedChevronLgDownUpIcon);
|
||||||
const findHelpPopover = () => wrapper.findComponent(HelpPopover);
|
const findHelpPopover = () => wrapper.findComponent(HelpPopover);
|
||||||
const findDynamicScroller = () => wrapper.findByTestId('dynamic-content-scroller');
|
const findDynamicScroller = () => wrapper.findByTestId('dynamic-content-scroller');
|
||||||
|
|
||||||
|
@ -282,6 +285,27 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
|
||||||
expect(findToggleButton().attributes('aria-label')).toBe('Show details');
|
expect(findToggleButton().attributes('aria-label')).toBe('Show details');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('displays the chevron correctly when toggle is clicked', async () => {
|
||||||
|
await createComponent({
|
||||||
|
propsData: {
|
||||||
|
isCollapsible: true,
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
content: '<b>More complex content</b>',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Vue compat doesn't know about component props if it extends other component
|
||||||
|
expect(
|
||||||
|
findToggleChevron().props('isOn') ?? parseBoolean(findToggleChevron().attributes('is-on')),
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
findToggleButton().vm.$emit('click');
|
||||||
|
await nextTick();
|
||||||
|
expect(
|
||||||
|
findToggleChevron().props('isOn') ?? parseBoolean(findToggleChevron().attributes('is-on')),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
it('does not display the content slot until toggle is clicked', async () => {
|
it('does not display the content slot until toggle is clicked', async () => {
|
||||||
await createComponent({
|
await createComponent({
|
||||||
propsData: {
|
propsData: {
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import Vue, { nextTick } from 'vue';
|
||||||
|
import VueApollo from 'vue-apollo';
|
||||||
|
import { GlDisclosureDropdown, GlToggle, GlDisclosureDropdownItem } from '@gitlab/ui';
|
||||||
|
import { createAlert } from '~/alert';
|
||||||
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||||
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
|
import WorkItemsListPreferences from '~/work_items/components/shared/work_item_list_preferences.vue';
|
||||||
|
import updateWorkItemsDisplaySettings from '~/work_items/graphql/update_user_preferences.mutation.graphql';
|
||||||
|
|
||||||
|
Vue.use(VueApollo);
|
||||||
|
|
||||||
|
jest.mock('~/alert');
|
||||||
|
|
||||||
|
describe('WorkItemsListPreferences', () => {
|
||||||
|
let wrapper;
|
||||||
|
let mockApolloProvider;
|
||||||
|
|
||||||
|
const successHandler = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
userPreferencesUpdate: {
|
||||||
|
__typename: 'UserPreferencesUpdatePayload',
|
||||||
|
userPreferences: {
|
||||||
|
__typename: 'UserPreferences',
|
||||||
|
workItemsDisplaySettings: { shouldOpenItemsInSidePanel: false },
|
||||||
|
},
|
||||||
|
errors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const createComponent = ({ props = {}, provide = {}, mutationHandler = successHandler } = {}) => {
|
||||||
|
mockApolloProvider = createMockApollo([[updateWorkItemsDisplaySettings, mutationHandler]]);
|
||||||
|
|
||||||
|
wrapper = shallowMount(WorkItemsListPreferences, {
|
||||||
|
apolloProvider: mockApolloProvider,
|
||||||
|
propsData: {
|
||||||
|
displaySettings: { shouldOpenItemsInSidePanel: true },
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
provide: { isSignedIn: true, ...provide },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
|
||||||
|
const findToggle = () => wrapper.findComponent(GlToggle);
|
||||||
|
const findDropdownItem = () => wrapper.findComponent(GlDisclosureDropdownItem);
|
||||||
|
|
||||||
|
describe('when user is signed in', () => {
|
||||||
|
it('renders dropdown with toggle', () => {
|
||||||
|
createComponent();
|
||||||
|
expect(findDropdown().exists()).toBe(true);
|
||||||
|
expect(findToggle().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders toggle with correct initial value', () => {
|
||||||
|
createComponent();
|
||||||
|
expect(findToggle().props('value')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles empty displaySettings gracefully', () => {
|
||||||
|
createComponent({ props: { displaySettings: {} } });
|
||||||
|
expect(findToggle().props('value')).toBe(true); // defaults to true
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when toggle is clicked', () => {
|
||||||
|
it('saves preference and emits event on success', async () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
findDropdownItem().vm.$emit('action');
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(successHandler).toHaveBeenCalledWith({
|
||||||
|
input: {
|
||||||
|
workItemsDisplaySettings: { shouldOpenItemsInSidePanel: false },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(wrapper.emitted('displaySettingsChanged')).toHaveLength(1);
|
||||||
|
expect(wrapper.emitted('displaySettingsChanged')[0][0]).toEqual({
|
||||||
|
shouldOpenItemsInSidePanel: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows loading state while saving', async () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
// Store the initial state
|
||||||
|
expect(findToggle().props('isLoading')).toBe(false);
|
||||||
|
|
||||||
|
findDropdownItem().vm.$emit('action');
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
// Check loading state
|
||||||
|
expect(findToggle().props('isLoading')).toBe(true);
|
||||||
|
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
// Check state after loading
|
||||||
|
expect(findToggle().props('isLoading')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles mutation errors gracefully', async () => {
|
||||||
|
const error = new Error('Network error');
|
||||||
|
const errorHandler = jest.fn().mockRejectedValue(error);
|
||||||
|
|
||||||
|
createComponent({ mutationHandler: errorHandler });
|
||||||
|
|
||||||
|
findDropdownItem().vm.$emit('action');
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(createAlert).toHaveBeenCalledWith({
|
||||||
|
message: 'Something went wrong while saving the preference.',
|
||||||
|
captureError: true,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
expect(wrapper.emitted('displaySettingsChanged')).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dropdown visibility', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows tooltip when dropdown is closed', () => {
|
||||||
|
expect(wrapper.vm.tooltipText).toBe('Display options');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides tooltip when dropdown is open', async () => {
|
||||||
|
findDropdown().vm.$emit('shown');
|
||||||
|
await nextTick();
|
||||||
|
expect(wrapper.vm.tooltipText).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when user is not signed in', () => {
|
||||||
|
it('does not render dropdown', () => {
|
||||||
|
createComponent({ provide: { isSignedIn: false } });
|
||||||
|
expect(findDropdown().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -23,6 +23,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
import { STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
|
import { STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
|
||||||
import { CREATED_DESC, UPDATED_DESC, urlSortParams } from '~/issues/list/constants';
|
import { CREATED_DESC, UPDATED_DESC, urlSortParams } from '~/issues/list/constants';
|
||||||
import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql';
|
import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql';
|
||||||
|
import getUserWorkItemsDisplaySettingsPreferences from '~/work_items/graphql/get_user_preferences.query.graphql';
|
||||||
import { scrollUp } from '~/lib/utils/scroll_utils';
|
import { scrollUp } from '~/lib/utils/scroll_utils';
|
||||||
import { getParameterByName, removeParams, updateHistory } from '~/lib/utils/url_utility';
|
import { getParameterByName, removeParams, updateHistory } from '~/lib/utils/url_utility';
|
||||||
import {
|
import {
|
||||||
|
@ -45,6 +46,7 @@ import {
|
||||||
TOKEN_TYPE_UPDATED,
|
TOKEN_TYPE_UPDATED,
|
||||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||||
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
|
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
|
||||||
|
import WorkItemUserPreferences from '~/work_items/components/shared/work_item_list_preferences.vue';
|
||||||
import CreateWorkItemModal from '~/work_items/components/create_work_item_modal.vue';
|
import CreateWorkItemModal from '~/work_items/components/create_work_item_modal.vue';
|
||||||
import WorkItemsListApp from '~/work_items/pages/work_items_list_app.vue';
|
import WorkItemsListApp from '~/work_items/pages/work_items_list_app.vue';
|
||||||
import getWorkItemStateCountsQuery from 'ee_else_ce/work_items/graphql/list/get_work_item_state_counts.query.graphql';
|
import getWorkItemStateCountsQuery from 'ee_else_ce/work_items/graphql/list/get_work_item_state_counts.query.graphql';
|
||||||
|
@ -94,6 +96,11 @@ describeSkipVue3(skipReason, () => {
|
||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValue(groupWorkItemStateCountsQueryResponse);
|
.mockResolvedValue(groupWorkItemStateCountsQueryResponse);
|
||||||
const mutationHandler = jest.fn().mockResolvedValue(setSortPreferenceMutationResponse);
|
const mutationHandler = jest.fn().mockResolvedValue(setSortPreferenceMutationResponse);
|
||||||
|
const mockPreferencesQueryHandler = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
currentUser: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const findIssuableList = () => wrapper.findComponent(IssuableList);
|
const findIssuableList = () => wrapper.findComponent(IssuableList);
|
||||||
const findIssueCardStatistics = () => wrapper.findComponent(IssueCardStatistics);
|
const findIssueCardStatistics = () => wrapper.findComponent(IssueCardStatistics);
|
||||||
|
@ -105,6 +112,7 @@ describeSkipVue3(skipReason, () => {
|
||||||
const findBulkEditStartButton = () => wrapper.find('[data-testid="bulk-edit-start-button"]');
|
const findBulkEditStartButton = () => wrapper.find('[data-testid="bulk-edit-start-button"]');
|
||||||
const findBulkEditSidebar = () => wrapper.findComponent(WorkItemBulkEditSidebar);
|
const findBulkEditSidebar = () => wrapper.findComponent(WorkItemBulkEditSidebar);
|
||||||
const findWorkItemListHeading = () => wrapper.findComponent(WorkItemListHeading);
|
const findWorkItemListHeading = () => wrapper.findComponent(WorkItemListHeading);
|
||||||
|
const findWorkItemUserPreferences = () => wrapper.findComponent(WorkItemUserPreferences);
|
||||||
|
|
||||||
const mountComponent = ({
|
const mountComponent = ({
|
||||||
provide = {},
|
provide = {},
|
||||||
|
@ -112,6 +120,7 @@ describeSkipVue3(skipReason, () => {
|
||||||
slimQueryHandler = defaultSlimQueryHandler,
|
slimQueryHandler = defaultSlimQueryHandler,
|
||||||
countsQueryHandler = defaultCountsQueryHandler,
|
countsQueryHandler = defaultCountsQueryHandler,
|
||||||
sortPreferenceMutationResponse = mutationHandler,
|
sortPreferenceMutationResponse = mutationHandler,
|
||||||
|
mockPreferencesHandler = mockPreferencesQueryHandler,
|
||||||
workItemsToggleEnabled = true,
|
workItemsToggleEnabled = true,
|
||||||
workItemPlanningView = false,
|
workItemPlanningView = false,
|
||||||
props = {},
|
props = {},
|
||||||
|
@ -131,6 +140,7 @@ describeSkipVue3(skipReason, () => {
|
||||||
[getWorkItemsSlimQuery, slimQueryHandler],
|
[getWorkItemsSlimQuery, slimQueryHandler],
|
||||||
[getWorkItemStateCountsQuery, countsQueryHandler],
|
[getWorkItemStateCountsQuery, countsQueryHandler],
|
||||||
[setSortPreferenceMutation, sortPreferenceMutationResponse],
|
[setSortPreferenceMutation, sortPreferenceMutationResponse],
|
||||||
|
[getUserWorkItemsDisplaySettingsPreferences, mockPreferencesHandler],
|
||||||
...additionalHandlers,
|
...additionalHandlers,
|
||||||
]),
|
]),
|
||||||
provide: {
|
provide: {
|
||||||
|
@ -722,6 +732,47 @@ describeSkipVue3(skipReason, () => {
|
||||||
expect(findDrawer().exists()).toBe(true);
|
expect(findDrawer().exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('display settings', () => {
|
||||||
|
it('updates displaySettings when displaySettingsChanged event is emitted', async () => {
|
||||||
|
mountComponent();
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
const newSettings = { shouldOpenItemsInSidePanel: false };
|
||||||
|
findWorkItemUserPreferences().vm.$emit('displaySettingsChanged', newSettings);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(findWorkItemUserPreferences().props('displaySettings')).toEqual(newSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('workItemDrawerEnabled with display settings', () => {
|
||||||
|
it('returns false when shouldOpenItemsInSidePanel is false', async () => {
|
||||||
|
const mockHandler = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
currentUser: {
|
||||||
|
id: 'gid://gitlab/User/1',
|
||||||
|
userPreferences: {
|
||||||
|
workItemsDisplaySettings: { shouldOpenItemsInSidePanel: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
mountComponent({
|
||||||
|
mockPreferencesHandler: mockHandler,
|
||||||
|
provide: {
|
||||||
|
glFeatures: { workItemViewForIssues: true },
|
||||||
|
isSignedIn: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForPromises();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(findIssuableList().props('preventRedirect')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('selecting issues', () => {
|
describe('selecting issues', () => {
|
||||||
const issue = workItemsQueryResponseCombined.data.namespace.workItems.nodes[0];
|
const issue = workItemsQueryResponseCombined.data.namespace.workItems.nodes[0];
|
||||||
const payload = {
|
const payload = {
|
||||||
|
|
|
@ -613,7 +613,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
||||||
:create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment,
|
:create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment,
|
||||||
:create_cluster, :read_cluster, :update_cluster, :admin_cluster,
|
:create_cluster, :read_cluster, :update_cluster, :admin_cluster,
|
||||||
:create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment,
|
:create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment,
|
||||||
:download_code, :build_download_code
|
:download_code, :build_download_code, :read_code
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1530,6 +1530,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
||||||
let!(:deploy_keys_project) { create(:deploy_keys_project, project: project, deploy_key: deploy_key) }
|
let!(:deploy_keys_project) { create(:deploy_keys_project, project: project, deploy_key: deploy_key) }
|
||||||
|
|
||||||
it { is_expected.to be_allowed(:download_code) }
|
it { is_expected.to be_allowed(:download_code) }
|
||||||
|
it { is_expected.to be_allowed(:read_code) }
|
||||||
it { is_expected.to be_disallowed(:push_code) }
|
it { is_expected.to be_disallowed(:push_code) }
|
||||||
it { is_expected.to be_disallowed(:read_project) }
|
it { is_expected.to be_disallowed(:read_project) }
|
||||||
end
|
end
|
||||||
|
@ -1538,12 +1539,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
||||||
let!(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) }
|
let!(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) }
|
||||||
|
|
||||||
it { is_expected.to be_allowed(:download_code) }
|
it { is_expected.to be_allowed(:download_code) }
|
||||||
|
it { is_expected.to be_allowed(:read_code) }
|
||||||
it { is_expected.to be_allowed(:push_code) }
|
it { is_expected.to be_allowed(:push_code) }
|
||||||
it { is_expected.to be_disallowed(:read_project) }
|
it { is_expected.to be_disallowed(:read_project) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the deploy key is not enabled in the project' do
|
context 'when the deploy key is not enabled in the project' do
|
||||||
it { is_expected.to be_disallowed(:download_code) }
|
it { is_expected.to be_disallowed(:download_code) }
|
||||||
|
it { is_expected.to be_disallowed(:read_code) }
|
||||||
it { is_expected.to be_disallowed(:push_code) }
|
it { is_expected.to be_disallowed(:push_code) }
|
||||||
it { is_expected.to be_disallowed(:read_project) }
|
it { is_expected.to be_disallowed(:read_project) }
|
||||||
end
|
end
|
||||||
|
@ -3690,6 +3693,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
||||||
with_them do
|
with_them do
|
||||||
it do
|
it do
|
||||||
expect(subject.can?(:download_code)).to be(allowed)
|
expect(subject.can?(:download_code)).to be(allowed)
|
||||||
|
expect(subject.can?(:read_code)).to be(allowed)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3710,33 +3714,13 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
||||||
with_them do
|
with_them do
|
||||||
it do
|
it do
|
||||||
expect(subject.can?(:download_code)).to be(allowed)
|
expect(subject.can?(:download_code)).to be(allowed)
|
||||||
|
expect(subject.can?(:read_code)).to be(allowed)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'read_code' do
|
|
||||||
let(:current_user) { create(:user) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(subject).to receive(:allowed?).and_call_original
|
|
||||||
allow(subject).to receive(:allowed?).with(:download_code).and_return(can_download_code)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when the current_user can download_code' do
|
|
||||||
let(:can_download_code) { true }
|
|
||||||
|
|
||||||
it { expect_allowed(:read_code) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when the current_user cannot download_code' do
|
|
||||||
let(:can_download_code) { false }
|
|
||||||
|
|
||||||
it { expect_disallowed(:read_code) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'read_namespace_catalog' do
|
describe 'read_namespace_catalog' do
|
||||||
let(:current_user) { owner }
|
let(:current_user) { owner }
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,16 @@ RSpec.describe Integrations::HarborSerializers::ArtifactEntity, feature_category
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when artifact has no tags' do
|
||||||
|
before do
|
||||||
|
artifact['tags'] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an empty array for tags' do
|
||||||
|
expect(subject[:tags]).to eq([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with data that may contain path traversal attacks' do
|
context 'with data that may contain path traversal attacks' do
|
||||||
before do
|
before do
|
||||||
artifact['digest'] = './../../../../../etc/hosts'
|
artifact['digest'] = './../../../../../etc/hosts'
|
||||||
|
|
|
@ -4,11 +4,11 @@ require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Commits::CherryPickService, feature_category: :source_code_management do
|
RSpec.describe Commits::CherryPickService, feature_category: :source_code_management do
|
||||||
let(:project) { create(:project, :repository) }
|
let(:project) { create(:project, :repository) }
|
||||||
# * ddd0f15ae83993f5cb66a927a28673882e99100b (HEAD -> master, origin/master, origin/HEAD) Merge branch 'po-fix-test-en
|
# * ddd0f15 (HEAD -> master, origin/master, origin/HEAD) Merge branch 'po-fix-test-en
|
||||||
# |\
|
# |\
|
||||||
# | * 2d1db523e11e777e49377cfb22d368deec3f0793 Correct test_env.rb path for adding branch
|
# | * 2d1db52 Correct test_env.rb path for adding branch
|
||||||
# |/
|
# |/
|
||||||
# * 1e292f8fedd741b75372e19097c76d327140c312 Merge branch 'cherry-pick-ce369011' into 'master'
|
# * 1e292f8 Merge branch 'cherry-pick-ce369011' into 'master'
|
||||||
|
|
||||||
let_it_be(:merge_commit_sha) { 'ddd0f15ae83993f5cb66a927a28673882e99100b' }
|
let_it_be(:merge_commit_sha) { 'ddd0f15ae83993f5cb66a927a28673882e99100b' }
|
||||||
let_it_be(:merge_base_sha) { '1e292f8fedd741b75372e19097c76d327140c312' }
|
let_it_be(:merge_base_sha) { '1e292f8fedd741b75372e19097c76d327140c312' }
|
||||||
|
@ -69,7 +69,16 @@ RSpec.describe Commits::CherryPickService, feature_category: :source_code_manage
|
||||||
it_behaves_like 'successful cherry-pick'
|
it_behaves_like 'successful cherry-pick'
|
||||||
|
|
||||||
context 'when picking a merge-request' do
|
context 'when picking a merge-request' do
|
||||||
let!(:merge_request) { create(:merge_request, :simple, :merged, author: user, source_project: project, merge_commit_sha: merge_commit_sha) }
|
let!(:merge_request) do
|
||||||
|
create(
|
||||||
|
:merge_request,
|
||||||
|
:simple,
|
||||||
|
:merged,
|
||||||
|
author: user,
|
||||||
|
source_project: project,
|
||||||
|
merge_commit_sha: merge_commit_sha
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it_behaves_like 'successful cherry-pick'
|
it_behaves_like 'successful cherry-pick'
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,22 @@ RSpec.shared_examples 'a harbor artifacts controller' do |args|
|
||||||
|
|
||||||
it_behaves_like 'responds with 200 status with json'
|
it_behaves_like 'responds with 200 status with json'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with artifacts that have no tags' do
|
||||||
|
let(:mock_artifacts) { [super()[0].merge(tags: nil)] }
|
||||||
|
|
||||||
|
subject do
|
||||||
|
get harbor_artifact_url(container, repository_id), headers: json_header
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'responds with 200 status with json'
|
||||||
|
|
||||||
|
it 'returns empty tags array' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(Gitlab::Json.parse(response.body).first['tags']).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with invalid params' do
|
context 'with invalid params' do
|
||||||
|
|
Loading…
Reference in New Issue