Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									e227f48234
								
							
						
					
					
						commit
						73d39cc50e
					
				|  | @ -1,5 +1,4 @@ | ||||||
| <script> | <script> | ||||||
| import { isEmpty } from 'lodash'; |  | ||||||
| import { GlAlert } from '@gitlab/ui'; | import { GlAlert } from '@gitlab/ui'; | ||||||
| import { __ } from '~/locale'; | import { __ } from '~/locale'; | ||||||
| import JobPill from './job_pill.vue'; | import JobPill from './job_pill.vue'; | ||||||
|  | @ -22,8 +21,6 @@ export default { | ||||||
|   errorTexts: { |   errorTexts: { | ||||||
|     [DRAW_FAILURE]: __('Could not draw the lines for job relationships'), |     [DRAW_FAILURE]: __('Could not draw the lines for job relationships'), | ||||||
|     [DEFAULT]: __('An unknown error occurred.'), |     [DEFAULT]: __('An unknown error occurred.'), | ||||||
|   }, |  | ||||||
|   warningTexts: { |  | ||||||
|     [EMPTY_PIPELINE_DATA]: __( |     [EMPTY_PIPELINE_DATA]: __( | ||||||
|       'The visualization will appear in this tab when the CI/CD configuration file is populated with valid syntax.', |       'The visualization will appear in this tab when the CI/CD configuration file is populated with valid syntax.', | ||||||
|     ), |     ), | ||||||
|  | @ -46,24 +43,24 @@ export default { | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|  |     hideGraph() { | ||||||
|  |       // We won't even try to render the graph with these condition | ||||||
|  |       // because it would cause additional errors down the line for the user | ||||||
|  |       // which is confusing. | ||||||
|  |       return this.isPipelineDataEmpty || this.isInvalidCiConfig; | ||||||
|  |     }, | ||||||
|     pipelineStages() { |     pipelineStages() { | ||||||
|       return this.pipelineData?.stages || []; |       return this.pipelineData?.stages || []; | ||||||
|     }, |     }, | ||||||
|     isPipelineDataEmpty() { |     isPipelineDataEmpty() { | ||||||
|       return !this.isInvalidCiConfig && isEmpty(this.pipelineStages); |       return !this.isInvalidCiConfig && this.pipelineStages.length === 0; | ||||||
|     }, |     }, | ||||||
|     isInvalidCiConfig() { |     isInvalidCiConfig() { | ||||||
|       return this.pipelineData?.status === CI_CONFIG_STATUS_INVALID; |       return this.pipelineData?.status === CI_CONFIG_STATUS_INVALID; | ||||||
|     }, |     }, | ||||||
|     showAlert() { |  | ||||||
|       return this.hasError || this.hasWarning; |  | ||||||
|     }, |  | ||||||
|     hasError() { |     hasError() { | ||||||
|       return this.failureType; |       return this.failureType; | ||||||
|     }, |     }, | ||||||
|     hasWarning() { |  | ||||||
|       return this.warning; |  | ||||||
|     }, |  | ||||||
|     hasHighlightedJob() { |     hasHighlightedJob() { | ||||||
|       return Boolean(this.highlightedJob); |       return Boolean(this.highlightedJob); | ||||||
|     }, |     }, | ||||||
|  | @ -75,26 +72,32 @@ export default { | ||||||
|       return this.warning; |       return this.warning; | ||||||
|     }, |     }, | ||||||
|     failure() { |     failure() { | ||||||
|       const text = this.$options.errorTexts[this.failureType] || this.$options.errorTexts[DEFAULT]; |       switch (this.failureType) { | ||||||
| 
 |         case DRAW_FAILURE: | ||||||
|       return { text, variant: 'danger', dismissible: true }; |  | ||||||
|     }, |  | ||||||
|     warning() { |  | ||||||
|       if (this.isPipelineDataEmpty) { |  | ||||||
|           return { |           return { | ||||||
|           text: this.$options.warningTexts[EMPTY_PIPELINE_DATA], |             text: this.$options.errorTexts[DRAW_FAILURE], | ||||||
|  |             variant: 'danger', | ||||||
|  |             dismissible: true, | ||||||
|  |           }; | ||||||
|  |         case EMPTY_PIPELINE_DATA: | ||||||
|  |           return { | ||||||
|  |             text: this.$options.errorTexts[EMPTY_PIPELINE_DATA], | ||||||
|             variant: 'tip', |             variant: 'tip', | ||||||
|             dismissible: false, |             dismissible: false, | ||||||
|           }; |           }; | ||||||
|       } else if (this.isInvalidCiConfig) { |         case INVALID_CI_CONFIG: | ||||||
|           return { |           return { | ||||||
|           text: this.$options.warningTexts[INVALID_CI_CONFIG], |             text: this.$options.errorTexts[INVALID_CI_CONFIG], | ||||||
|             variant: 'danger', |             variant: 'danger', | ||||||
|             dismissible: false, |             dismissible: false, | ||||||
|           }; |           }; | ||||||
|  |         default: | ||||||
|  |           return { | ||||||
|  |             text: this.$options.errorTexts[DEFAULT], | ||||||
|  |             variant: 'danger', | ||||||
|  |             dismissible: true, | ||||||
|  |           }; | ||||||
|       } |       } | ||||||
| 
 |  | ||||||
|       return null; |  | ||||||
|     }, |     }, | ||||||
|     viewBox() { |     viewBox() { | ||||||
|       return [0, 0, this.width, this.height]; |       return [0, 0, this.width, this.height]; | ||||||
|  | @ -122,6 +125,24 @@ export default { | ||||||
|       return []; |       return []; | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|  |   watch: { | ||||||
|  |     isPipelineDataEmpty: { | ||||||
|  |       immediate: true, | ||||||
|  |       handler(isDataEmpty) { | ||||||
|  |         if (isDataEmpty) { | ||||||
|  |           this.reportFailure(EMPTY_PIPELINE_DATA); | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     isInvalidCiConfig: { | ||||||
|  |       immediate: true, | ||||||
|  |       handler(isInvalid) { | ||||||
|  |         if (isInvalid) { | ||||||
|  |           this.reportFailure(INVALID_CI_CONFIG); | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     if (!this.isPipelineDataEmpty && !this.isInvalidCiConfig) { |     if (!this.isPipelineDataEmpty && !this.isInvalidCiConfig) { | ||||||
|       // This guarantee that all sub-elements are rendered |       // This guarantee that all sub-elements are rendered | ||||||
|  | @ -201,7 +222,7 @@ export default { | ||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <gl-alert |     <gl-alert | ||||||
|       v-if="showAlert" |       v-if="hasError" | ||||||
|       :variant="alert.variant" |       :variant="alert.variant" | ||||||
|       :dismissible="alert.dismissible" |       :dismissible="alert.dismissible" | ||||||
|       @dismiss="alert.dismissible ? resetFailure : null" |       @dismiss="alert.dismissible ? resetFailure : null" | ||||||
|  | @ -209,7 +230,7 @@ export default { | ||||||
|       {{ alert.text }} |       {{ alert.text }} | ||||||
|     </gl-alert> |     </gl-alert> | ||||||
|     <div |     <div | ||||||
|       v-if="!hasWarning" |       v-if="!hideGraph" | ||||||
|       :id="$options.CONTAINER_ID" |       :id="$options.CONTAINER_ID" | ||||||
|       :ref="$options.CONTAINER_REF" |       :ref="$options.CONTAINER_REF" | ||||||
|       class="gl-display-flex gl-bg-gray-50 gl-px-4 gl-overflow-auto gl-relative gl-py-7" |       class="gl-display-flex gl-bg-gray-50 gl-px-4 gl-overflow-auto gl-relative gl-py-7" | ||||||
|  |  | ||||||
|  | @ -4,6 +4,9 @@ | ||||||
| # | # | ||||||
| # Used to filter Issues and MergeRequests collections by set of params | # Used to filter Issues and MergeRequests collections by set of params | ||||||
| # | # | ||||||
|  | # Note: This class is NOT meant to be instantiated. Instead you should | ||||||
|  | #       look at IssuesFinder or EpicsFinder, which inherit from this. | ||||||
|  | # | ||||||
| # Arguments: | # Arguments: | ||||||
| #   klass - actual class like Issue or MergeRequest | #   klass - actual class like Issue or MergeRequest | ||||||
| #   current_user - which user use | #   current_user - which user use | ||||||
|  | @ -92,6 +95,10 @@ class IssuableFinder | ||||||
|     IssuableFinder::Params |     IssuableFinder::Params | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def klass | ||||||
|  |     raise NotImplementedError | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def initialize(current_user, params = {}) |   def initialize(current_user, params = {}) | ||||||
|     @current_user = current_user |     @current_user = current_user | ||||||
|     @params = params_class.new(params, current_user, klass) |     @params = params_class.new(params, current_user, klass) | ||||||
|  | @ -451,6 +458,7 @@ class IssuableFinder | ||||||
| 
 | 
 | ||||||
|   def by_release(items) |   def by_release(items) | ||||||
|     return items unless params.releases? |     return items unless params.releases? | ||||||
|  |     return items if params.group? # don't allow release filtering at group level | ||||||
| 
 | 
 | ||||||
|     if params.filter_by_no_release? |     if params.filter_by_no_release? | ||||||
|       items.without_release |       items.without_release | ||||||
|  |  | ||||||
|  | @ -108,16 +108,8 @@ class IssuableFinder | ||||||
|       project_id.present? |       project_id.present? | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def group |     def group? | ||||||
|       strong_memoize(:group) do |       group_id.present? | ||||||
|         if params[:group_id].is_a?(Group) |  | ||||||
|           params[:group_id] |  | ||||||
|         elsif params[:group_id].present? |  | ||||||
|           Group.find(params[:group_id]) |  | ||||||
|         else |  | ||||||
|           nil |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def related_groups |     def related_groups | ||||||
|  | @ -143,10 +135,25 @@ class IssuableFinder | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     def group | ||||||
|  |       strong_memoize(:group) do | ||||||
|  |         next nil unless group? | ||||||
|  | 
 | ||||||
|  |         group = group_id.is_a?(Group) ? group_id : Group.find(group_id) | ||||||
|  |         group = nil unless Ability.allowed?(current_user, :read_group, group) | ||||||
|  | 
 | ||||||
|  |         group | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     def project_id |     def project_id | ||||||
|       params[:project_id] |       params[:project_id] | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     def group_id | ||||||
|  |       params[:group_id] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     def projects |     def projects | ||||||
|       strong_memoize(:projects) do |       strong_memoize(:projects) do | ||||||
|         next [project] if project? |         next [project] if project? | ||||||
|  | @ -216,14 +223,14 @@ class IssuableFinder | ||||||
|       strong_memoize(:milestones) do |       strong_memoize(:milestones) do | ||||||
|         if milestones? |         if milestones? | ||||||
|           if project? |           if project? | ||||||
|             group_id = project.group&.id |             project_group_id = project.group&.id | ||||||
|             project_id = project.id |             project_id = project.id | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|           group_id = group.id if group |           project_group_id = group.id if group | ||||||
| 
 | 
 | ||||||
|           search_params = |           search_params = | ||||||
|             { title: params[:milestone_title], project_ids: project_id, group_ids: group_id } |             { title: params[:milestone_title], project_ids: project_id, group_ids: project_group_id } | ||||||
| 
 | 
 | ||||||
|           MilestonesFinder.new(search_params).execute # rubocop: disable CodeReuse/Finder |           MilestonesFinder.new(search_params).execute # rubocop: disable CodeReuse/Finder | ||||||
|         else |         else | ||||||
|  |  | ||||||
|  | @ -31,7 +31,7 @@ module Issues | ||||||
| 
 | 
 | ||||||
|         closed_via = _("commit %{commit_id}") % { commit_id: closed_via.id } if closed_via.is_a?(Commit) |         closed_via = _("commit %{commit_id}") % { commit_id: closed_via.id } if closed_via.is_a?(Commit) | ||||||
| 
 | 
 | ||||||
|         notification_service.async.close_issue(issue, current_user, closed_via: closed_via) if notifications |         notification_service.async.close_issue(issue, current_user, { closed_via: closed_via }) if notifications | ||||||
|         todo_service.close_issue(issue, current_user) |         todo_service.close_issue(issue, current_user) | ||||||
|         resolve_alert(issue) |         resolve_alert(issue) | ||||||
|         execute_hooks(issue, 'close') |         execute_hooks(issue, 'close') | ||||||
|  |  | ||||||
|  | @ -118,8 +118,8 @@ class NotificationService | ||||||
|   #  * project team members with notification level higher then Participating |   #  * project team members with notification level higher then Participating | ||||||
|   #  * users with custom level checked with "close issue" |   #  * users with custom level checked with "close issue" | ||||||
|   # |   # | ||||||
|   def close_issue(issue, current_user, closed_via: nil) |   def close_issue(issue, current_user, params = {}) | ||||||
|     close_resource_email(issue, current_user, :closed_issue_email, closed_via: closed_via) |     close_resource_email(issue, current_user, :closed_issue_email, closed_via: params[:closed_via]) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   # When we reassign an issue we should send an email to: |   # When we reassign an issue we should send an email to: | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Don't allow filtering by release tag on groups. | ||||||
|  | merge_request: 50457 | ||||||
|  | author: | ||||||
|  | type: fixed | ||||||
|  | @ -541,13 +541,16 @@ To enable or disable tracking for specific event within <https://gitlab.com> or | ||||||
| /chatops run feature set <feature_name> false | /chatops run feature set <feature_name> false | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ##### Known events in usage data payload | ##### Known events are added automatically in usage data payload | ||||||
| 
 | 
 | ||||||
| All events added in [`known_events/common.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data_counters/known_events/common.yml) are automatically added to usage data generation under the `redis_hll_counters` key. This column is stored in [version-app as a JSON](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/db/schema.rb#L209). | All events added in [`known_events/common.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data_counters/known_events/common.yml) are automatically added to usage data generation under the `redis_hll_counters` key. This column is stored in [version-app as a JSON](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/db/schema.rb#L209). | ||||||
| For each event we add metrics for the weekly and monthly time frames, and totals for each where applicable: | For each event we add metrics for the weekly and monthly time frames, and totals for each where applicable: | ||||||
| 
 | 
 | ||||||
| - `#{event_name}_weekly`: Data for 7 days for daily [aggregation](#adding-new-events) events and data for the last complete week for weekly [aggregation](#adding-new-events) events. | - `#{event_name}_weekly`: Data for 7 days for daily [aggregation](#adding-new-events) events and data for the last complete week for weekly [aggregation](#adding-new-events) events. | ||||||
| - `#{event_name}_monthly`: Data for 28 days for daily [aggregation](#adding-new-events) events and data for the last 4 complete weeks for weekly [aggregation](#adding-new-events) events. | - `#{event_name}_monthly`: Data for 28 days for daily [aggregation](#adding-new-events) events and data for the last 4 complete weeks for weekly [aggregation](#adding-new-events) events. | ||||||
|  | 
 | ||||||
|  | Redis HLL implementation calculates automatic total metrics, if there are more than one metric for the same category, aggregation and Redis slot.  | ||||||
|  | 
 | ||||||
| - `#{category}_total_unique_counts_weekly`: Total unique counts for events in the same category for the last 7 days or the last complete week, if events are in the same Redis slot and we have more than one metric. | - `#{category}_total_unique_counts_weekly`: Total unique counts for events in the same category for the last 7 days or the last complete week, if events are in the same Redis slot and we have more than one metric. | ||||||
| - `#{category}_total_unique_counts_monthly`: Total unique counts for events in same category for the last 28 days or the last 4 complete weeks, if events are in the same Redis slot and we have more than one metric. | - `#{category}_total_unique_counts_monthly`: Total unique counts for events in same category for the last 28 days or the last 4 complete weeks, if events are in the same Redis slot and we have more than one metric. | ||||||
| 
 | 
 | ||||||
|  | @ -792,7 +795,7 @@ In order to add data for aggregated metrics into Usage Ping payload you should a | ||||||
| - operator: operator that defines how aggregated metric data is counted. Available operators are: | - operator: operator that defines how aggregated metric data is counted. Available operators are: | ||||||
|   - `OR`: removes duplicates and counts all entries that triggered any of listed events |   - `OR`: removes duplicates and counts all entries that triggered any of listed events | ||||||
|   - `AND`: removes duplicates and counts all elements that were observed triggering all of following events |   - `AND`: removes duplicates and counts all elements that were observed triggering all of following events | ||||||
| - events: list of events names (from [`known_events.yml`](#known-events-in-usage-data-payload)) to aggregate into metric. All events in this list must have the same `redis_slot` and `aggregation` attributes. | - events: list of events names (from [`known_events.yml`](#known-events-are-added-automatically-in-usage-data-payload)) to aggregate into metric. All events in this list must have the same `redis_slot` and `aggregation` attributes. | ||||||
| - feature_flag: name of [development feature flag](../feature_flags/development.md#development-type) that is checked before | - feature_flag: name of [development feature flag](../feature_flags/development.md#development-type) that is checked before | ||||||
| metrics aggregation is performed. Corresponding feature flag should have `default_enabled` attribute set to `false`. | metrics aggregation is performed. Corresponding feature flag should have `default_enabled` attribute set to `false`. | ||||||
| `feature_flag` attribute is **OPTIONAL**  and can be omitted, when `feature_flag` is missing no feature flag is checked. | `feature_flag` attribute is **OPTIONAL**  and can be omitted, when `feature_flag` is missing no feature flag is checked. | ||||||
|  |  | ||||||
|  | @ -3,8 +3,8 @@ | ||||||
| module API | module API | ||||||
|   module Helpers |   module Helpers | ||||||
|     module Pagination |     module Pagination | ||||||
|       def paginate(*args) |       def paginate(*args, **kwargs) | ||||||
|         Gitlab::Pagination::OffsetPagination.new(self).paginate(*args) |         Gitlab::Pagination::OffsetPagination.new(self).paginate(*args, **kwargs) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -29037,7 +29037,7 @@ msgstr "" | ||||||
| msgid "ThreatMonitoring|Time" | msgid "ThreatMonitoring|Time" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "ThreatMonitoring|To view this data, ensure you have configured an environment for this project and that at least one threat monitoring feature is enabled." | msgid "ThreatMonitoring|To view this data, ensure you have configured an environment for this project and that at least one threat monitoring feature is enabled. %{linkStart}More information%{linkEnd}" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "ThreatMonitoring|Total Packets" | msgid "ThreatMonitoring|Total Packets" | ||||||
|  |  | ||||||
|  | @ -13,7 +13,27 @@ RSpec.describe IssuesFinder do | ||||||
|       let(:scope) { 'all' } |       let(:scope) { 'all' } | ||||||
| 
 | 
 | ||||||
|       it 'returns all issues' do |       it 'returns all issues' do | ||||||
|         expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) |         expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'user does not have read permissions' do | ||||||
|  |         let(:search_user) { user2 } | ||||||
|  | 
 | ||||||
|  |         context 'when filtering by project id' do | ||||||
|  |           let(:params) { { project_id: project1.id } } | ||||||
|  | 
 | ||||||
|  |           it 'returns no issues' do | ||||||
|  |             expect(issues).to be_empty | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when filtering by group id' do | ||||||
|  |           let(:params) { { group_id: group.id } } | ||||||
|  | 
 | ||||||
|  |           it 'returns no issues' do | ||||||
|  |             expect(issues).to be_empty | ||||||
|  |           end | ||||||
|  |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'assignee filtering' do |       context 'assignee filtering' do | ||||||
|  | @ -21,7 +41,7 @@ RSpec.describe IssuesFinder do | ||||||
| 
 | 
 | ||||||
|         it_behaves_like 'assignee ID filter' do |         it_behaves_like 'assignee ID filter' do | ||||||
|           let(:params) { { assignee_id: user.id } } |           let(:params) { { assignee_id: user.id } } | ||||||
|           let(:expected_issuables) { [issue1, issue2] } |           let(:expected_issuables) { [issue1, issue2, issue5] } | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         it_behaves_like 'assignee NOT ID filter' do |         it_behaves_like 'assignee NOT ID filter' do | ||||||
|  | @ -59,7 +79,25 @@ RSpec.describe IssuesFinder do | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         it_behaves_like 'any assignee filter' do |         it_behaves_like 'any assignee filter' do | ||||||
|           let(:expected_issuables) { [issue1, issue2, issue3] } |           let(:expected_issuables) { [issue1, issue2, issue3, issue5] } | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'filtering by release' do | ||||||
|  |         context 'when the release tag is none' do | ||||||
|  |           let(:params) { { release_tag: 'none' } } | ||||||
|  | 
 | ||||||
|  |           it 'returns issues without releases' do | ||||||
|  |             expect(issues).to contain_exactly(issue2, issue3, issue4, issue5) | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when the release tag exists' do | ||||||
|  |           let(:params) { { project_id: project1.id, release_tag: release.tag } } | ||||||
|  | 
 | ||||||
|  |           it 'returns the issues associated with that release' do | ||||||
|  |             expect(issues).to contain_exactly(issue1) | ||||||
|  |           end | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  | @ -68,7 +106,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { projects: [project1.id] } } |           let(:params) { { projects: [project1.id] } } | ||||||
| 
 | 
 | ||||||
|           it 'returns the issue belonging to the projects' do |           it 'returns the issue belonging to the projects' do | ||||||
|             expect(issues).to contain_exactly(issue1) |             expect(issues).to contain_exactly(issue1, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -76,7 +114,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { projects: Project.id_in(project1.id) } } |           let(:params) { { projects: Project.id_in(project1.id) } } | ||||||
| 
 | 
 | ||||||
|           it 'returns the issue belonging to the projects' do |           it 'returns the issue belonging to the projects' do | ||||||
|             expect(issues).to contain_exactly(issue1) |             expect(issues).to contain_exactly(issue1, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|  | @ -86,7 +124,7 @@ RSpec.describe IssuesFinder do | ||||||
| 
 | 
 | ||||||
|         context 'when include_subgroup param not set' do |         context 'when include_subgroup param not set' do | ||||||
|           it 'returns all group issues' do |           it 'returns all group issues' do | ||||||
|             expect(issues).to contain_exactly(issue1) |             expect(issues).to contain_exactly(issue1, issue5) | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|           context 'when projects outside the group are passed' do |           context 'when projects outside the group are passed' do | ||||||
|  | @ -101,7 +139,7 @@ RSpec.describe IssuesFinder do | ||||||
|             let(:params) { { group_id: group.id, projects: [project1.id] } } |             let(:params) { { group_id: group.id, projects: [project1.id] } } | ||||||
| 
 | 
 | ||||||
|             it 'returns the issue within the group and projects' do |             it 'returns the issue within the group and projects' do | ||||||
|               expect(issues).to contain_exactly(issue1) |               expect(issues).to contain_exactly(issue1, issue5) | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|  | @ -109,7 +147,15 @@ RSpec.describe IssuesFinder do | ||||||
|             let(:params) { { group_id: group.id, projects: Project.id_in(project1.id) } } |             let(:params) { { group_id: group.id, projects: Project.id_in(project1.id) } } | ||||||
| 
 | 
 | ||||||
|             it 'returns the issue within the group and projects' do |             it 'returns the issue within the group and projects' do | ||||||
|               expect(issues).to contain_exactly(issue1) |               expect(issues).to contain_exactly(issue1, issue5) | ||||||
|  |             end | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           context 'when release_tag is passed as a parameter' do | ||||||
|  |             let(:params) { { group_id: group.id, release_tag: 'dne-release-tag' } } | ||||||
|  | 
 | ||||||
|  |             it 'ignores the release_tag parameter' do | ||||||
|  |               expect(issues).to contain_exactly(issue1, issue5) | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|  | @ -120,7 +166,7 @@ RSpec.describe IssuesFinder do | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|           it 'returns all group and subgroup issues' do |           it 'returns all group and subgroup issues' do | ||||||
|             expect(issues).to contain_exactly(issue1, issue4) |             expect(issues).to contain_exactly(issue1, issue4, issue5) | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|           context 'when mixed projects are passed' do |           context 'when mixed projects are passed' do | ||||||
|  | @ -145,7 +191,7 @@ RSpec.describe IssuesFinder do | ||||||
|         let(:params) { { not: { author_id: user2.id } } } |         let(:params) { { not: { author_id: user2.id } } } | ||||||
| 
 | 
 | ||||||
|         it 'returns issues not created by that user' do |         it 'returns issues not created by that user' do | ||||||
|           expect(issues).to contain_exactly(issue1, issue2, issue4) |           expect(issues).to contain_exactly(issue1, issue2, issue4, issue5) | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  | @ -175,7 +221,7 @@ RSpec.describe IssuesFinder do | ||||||
|         let(:params) { { not: { milestone_title: milestone.title } } } |         let(:params) { { not: { milestone_title: milestone.title } } } | ||||||
| 
 | 
 | ||||||
|         it 'returns issues not assigned to that milestone' do |         it 'returns issues not assigned to that milestone' do | ||||||
|           expect(issues).to contain_exactly(issue2, issue3, issue4) |           expect(issues).to contain_exactly(issue2, issue3, issue4, issue5) | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  | @ -199,7 +245,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { not: { milestone_title: group_milestone.title } } } |           let(:params) { { not: { milestone_title: group_milestone.title } } } | ||||||
| 
 | 
 | ||||||
|           it 'returns issues not assigned to that group milestone' do |           it 'returns issues not assigned to that group milestone' do | ||||||
|             expect(issues).to contain_exactly(issue1, issue4) |             expect(issues).to contain_exactly(issue1, issue4, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|  | @ -208,13 +254,13 @@ RSpec.describe IssuesFinder do | ||||||
|         let(:params) { { milestone_title: 'None' } } |         let(:params) { { milestone_title: 'None' } } | ||||||
| 
 | 
 | ||||||
|         it 'returns issues with no milestone' do |         it 'returns issues with no milestone' do | ||||||
|           expect(issues).to contain_exactly(issue2, issue3, issue4) |           expect(issues).to contain_exactly(issue2, issue3, issue4, issue5) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         it 'returns issues with no milestone (deprecated)' do |         it 'returns issues with no milestone (deprecated)' do | ||||||
|           params[:milestone_title] = Milestone::None.title |           params[:milestone_title] = Milestone::None.title | ||||||
| 
 | 
 | ||||||
|           expect(issues).to contain_exactly(issue2, issue3, issue4) |           expect(issues).to contain_exactly(issue2, issue3, issue4, issue5) | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  | @ -343,7 +389,7 @@ RSpec.describe IssuesFinder do | ||||||
|             let(:params) { { not: { label_name: label.title } } } |             let(:params) { { not: { label_name: label.title } } } | ||||||
| 
 | 
 | ||||||
|             it 'returns issues that do not have that label' do |             it 'returns issues that do not have that label' do | ||||||
|               expect(issues).to contain_exactly(issue1, issue3, issue4) |               expect(issues).to contain_exactly(issue1, issue3, issue4, issue5) | ||||||
|             end |             end | ||||||
| 
 | 
 | ||||||
|             # IssuableFinder first filters using the outer params (the ones not inside the `not` key.) |             # IssuableFinder first filters using the outer params (the ones not inside the `not` key.) | ||||||
|  | @ -383,7 +429,7 @@ RSpec.describe IssuesFinder do | ||||||
|             let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } } |             let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } } | ||||||
| 
 | 
 | ||||||
|             it 'returns issues that do not have any of the labels provided' do |             it 'returns issues that do not have any of the labels provided' do | ||||||
|               expect(issues).to contain_exactly(issue1, issue4) |               expect(issues).to contain_exactly(issue1, issue4, issue5) | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|  | @ -405,7 +451,7 @@ RSpec.describe IssuesFinder do | ||||||
|             let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } } |             let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } } | ||||||
| 
 | 
 | ||||||
|             it 'returns issues that do not have ANY ONE of the labels provided' do |             it 'returns issues that do not have ANY ONE of the labels provided' do | ||||||
|               expect(issues).to contain_exactly(issue1, issue4) |               expect(issues).to contain_exactly(issue1, issue4, issue5) | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|  | @ -414,7 +460,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { label_name: described_class::Params::FILTER_NONE } } |           let(:params) { { label_name: described_class::Params::FILTER_NONE } } | ||||||
| 
 | 
 | ||||||
|           it 'returns issues with no labels' do |           it 'returns issues with no labels' do | ||||||
|             expect(issues).to contain_exactly(issue1, issue4) |             expect(issues).to contain_exactly(issue1, issue4, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -483,14 +529,14 @@ RSpec.describe IssuesFinder do | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'filtering by issues iids' do |       context 'filtering by issues iids' do | ||||||
|         let(:params) { { iids: issue3.iid } } |         let(:params) { { iids: [issue3.iid] } } | ||||||
| 
 | 
 | ||||||
|         it 'returns issues with iids match' do |         it 'returns issues where iids match' do | ||||||
|           expect(issues).to contain_exactly(issue3) |           expect(issues).to contain_exactly(issue3, issue5) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         context 'using NOT' do |         context 'using NOT' do | ||||||
|           let(:params) { { not: { iids: issue3.iid } } } |           let(:params) { { not: { iids: [issue3.iid] } } } | ||||||
| 
 | 
 | ||||||
|           it 'returns issues with no iids match' do |           it 'returns issues with no iids match' do | ||||||
|             expect(issues).to contain_exactly(issue1, issue2, issue4) |             expect(issues).to contain_exactly(issue1, issue2, issue4) | ||||||
|  | @ -503,7 +549,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { state: 'opened' } } |           let(:params) { { state: 'opened' } } | ||||||
| 
 | 
 | ||||||
|           it 'returns only opened issues' do |           it 'returns only opened issues' do | ||||||
|             expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) |             expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -519,7 +565,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { state: 'all' } } |           let(:params) { { state: 'all' } } | ||||||
| 
 | 
 | ||||||
|           it 'returns all issues' do |           it 'returns all issues' do | ||||||
|             expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4) |             expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -527,7 +573,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { state: 'invalid_state' } } |           let(:params) { { state: 'invalid_state' } } | ||||||
| 
 | 
 | ||||||
|           it 'returns all issues' do |           it 'returns all issues' do | ||||||
|             expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4) |             expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|  | @ -619,7 +665,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { my_reaction_emoji: 'None' } } |           let(:params) { { my_reaction_emoji: 'None' } } | ||||||
| 
 | 
 | ||||||
|           it 'returns issues that the user did not react to' do |           it 'returns issues that the user did not react to' do | ||||||
|             expect(issues).to contain_exactly(issue2, issue4) |             expect(issues).to contain_exactly(issue2, issue4, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -642,7 +688,7 @@ RSpec.describe IssuesFinder do | ||||||
|             let(:params) { { not: { my_reaction_emoji: 'thumbsup' } } } |             let(:params) { { not: { my_reaction_emoji: 'thumbsup' } } } | ||||||
| 
 | 
 | ||||||
|             it 'returns issues that the user did not thumbsup to' do |             it 'returns issues that the user did not thumbsup to' do | ||||||
|               expect(issues).to contain_exactly(issue2, issue3, issue4) |               expect(issues).to contain_exactly(issue2, issue3, issue4, issue5) | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|  | @ -676,7 +722,7 @@ RSpec.describe IssuesFinder do | ||||||
|             let(:params) { { not: { my_reaction_emoji: 'thumbsdown' } } } |             let(:params) { { not: { my_reaction_emoji: 'thumbsdown' } } } | ||||||
| 
 | 
 | ||||||
|             it 'returns issues that the user thumbsdown to' do |             it 'returns issues that the user thumbsdown to' do | ||||||
|               expect(issues).to contain_exactly(issue1, issue2, issue4) |               expect(issues).to contain_exactly(issue1, issue2, issue4, issue5) | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|  | @ -687,7 +733,7 @@ RSpec.describe IssuesFinder do | ||||||
| 
 | 
 | ||||||
|         context 'no filtering' do |         context 'no filtering' do | ||||||
|           it 'returns all issues' do |           it 'returns all issues' do | ||||||
|             expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, confidential_issue) |             expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5, confidential_issue) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -703,7 +749,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { confidential: false } } |           let(:params) { { confidential: false } } | ||||||
| 
 | 
 | ||||||
|           it 'returns only confdential issues' do |           it 'returns only confdential issues' do | ||||||
|             expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) |             expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|  | @ -715,7 +761,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { issue_types: [] } } |           let(:params) { { issue_types: [] } } | ||||||
| 
 | 
 | ||||||
|           it 'returns all issues' do |           it 'returns all issues' do | ||||||
|             expect(issues).to contain_exactly(incident_issue, issue1, issue2, issue3, issue4) |             expect(issues).to contain_exactly(incident_issue, issue1, issue2, issue3, issue4, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -731,7 +777,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { issue_types: ['issue'] } } |           let(:params) { { issue_types: ['issue'] } } | ||||||
| 
 | 
 | ||||||
|           it 'returns all issues with type issue' do |           it 'returns all issues with type issue' do | ||||||
|             expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) |             expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -739,7 +785,7 @@ RSpec.describe IssuesFinder do | ||||||
|           let(:params) { { issue_types: %w(issue incident) } } |           let(:params) { { issue_types: %w(issue incident) } } | ||||||
| 
 | 
 | ||||||
|           it 'returns all issues' do |           it 'returns all issues' do | ||||||
|             expect(issues).to contain_exactly(incident_issue, issue1, issue2, issue3, issue4) |             expect(issues).to contain_exactly(incident_issue, issue1, issue2, issue3, issue4, issue5) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -790,14 +836,14 @@ RSpec.describe IssuesFinder do | ||||||
|       let(:scope) { 'assigned_to_me' } |       let(:scope) { 'assigned_to_me' } | ||||||
| 
 | 
 | ||||||
|       it 'returns issue assigned to the user' do |       it 'returns issue assigned to the user' do | ||||||
|         expect(issues).to contain_exactly(issue1, issue2) |         expect(issues).to contain_exactly(issue1, issue2, issue5) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'filtering by project' do |       context 'filtering by project' do | ||||||
|         let(:params) { { project_id: project1.id } } |         let(:params) { { project_id: project1.id } } | ||||||
| 
 | 
 | ||||||
|         it 'returns issues assigned to the user in that project' do |         it 'returns issues assigned to the user in that project' do | ||||||
|           expect(issues).to contain_exactly(issue1) |           expect(issues).to contain_exactly(issue1, issue5) | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  | @ -839,7 +885,7 @@ RSpec.describe IssuesFinder do | ||||||
|         let(:params) { base_params.merge(due_date: Issue::NoDueDate.name) } |         let(:params) { base_params.merge(due_date: Issue::NoDueDate.name) } | ||||||
| 
 | 
 | ||||||
|         it 'returns issues with no due date' do |         it 'returns issues with no due date' do | ||||||
|           expect(issues).to contain_exactly(issue1) |           expect(issues).to contain_exactly(issue1, issue5) | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  | @ -875,7 +921,7 @@ RSpec.describe IssuesFinder do | ||||||
|     it 'returns the number of rows for the default state' do |     it 'returns the number of rows for the default state' do | ||||||
|       finder = described_class.new(admin) |       finder = described_class.new(admin) | ||||||
| 
 | 
 | ||||||
|       expect(finder.row_count).to eq(4) |       expect(finder.row_count).to eq(5) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'returns the number of rows for a given state' do |     it 'returns the number of rows for a given state' do | ||||||
|  |  | ||||||
|  | @ -37,7 +37,7 @@ describe('pipeline graph component', () => { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('renders an empty section', () => { |     it('renders an empty section', () => { | ||||||
|       expect(wrapper.text()).toBe(wrapper.vm.$options.warningTexts[EMPTY_PIPELINE_DATA]); |       expect(wrapper.text()).toBe(wrapper.vm.$options.errorTexts[EMPTY_PIPELINE_DATA]); | ||||||
|       expect(findPipelineGraph().exists()).toBe(false); |       expect(findPipelineGraph().exists()).toBe(false); | ||||||
|       expect(findAllStagePills()).toHaveLength(0); |       expect(findAllStagePills()).toHaveLength(0); | ||||||
|       expect(findAllJobPills()).toHaveLength(0); |       expect(findAllJobPills()).toHaveLength(0); | ||||||
|  | @ -51,7 +51,7 @@ describe('pipeline graph component', () => { | ||||||
| 
 | 
 | ||||||
|     it('renders an error message and does not render the graph', () => { |     it('renders an error message and does not render the graph', () => { | ||||||
|       expect(findAlert().exists()).toBe(true); |       expect(findAlert().exists()).toBe(true); | ||||||
|       expect(findAlert().text()).toBe(wrapper.vm.$options.warningTexts[INVALID_CI_CONFIG]); |       expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[INVALID_CI_CONFIG]); | ||||||
|       expect(findPipelineGraph().exists()).toBe(false); |       expect(findPipelineGraph().exists()).toBe(false); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  | @ -8,13 +8,53 @@ RSpec.shared_context 'IssuesFinder context' do | ||||||
|   let_it_be(:project1, reload: true) { create(:project, group: group) } |   let_it_be(:project1, reload: true) { create(:project, group: group) } | ||||||
|   let_it_be(:project2, reload: true) { create(:project) } |   let_it_be(:project2, reload: true) { create(:project) } | ||||||
|   let_it_be(:project3, reload: true) { create(:project, group: subgroup) } |   let_it_be(:project3, reload: true) { create(:project, group: subgroup) } | ||||||
|   let_it_be(:milestone) { create(:milestone, project: project1) } |   let_it_be(:release) { create(:release, project: project1, tag: 'v1.0.0') } | ||||||
|  |   let_it_be(:milestone) { create(:milestone, project: project1, releases: [release]) } | ||||||
|   let_it_be(:label) { create(:label, project: project2) } |   let_it_be(:label) { create(:label, project: project2) } | ||||||
|   let_it_be(:label2) { create(:label, project: project2) } |   let_it_be(:label2) { create(:label, project: project2) } | ||||||
|   let_it_be(:issue1, reload: true) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) } |   let_it_be(:issue1, reload: true) do | ||||||
|   let_it_be(:issue2, reload: true) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) } |     create(:issue, | ||||||
|   let_it_be(:issue3, reload: true) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) } |            author: user, | ||||||
|  |            assignees: [user], | ||||||
|  |            project: project1, | ||||||
|  |            milestone: milestone, | ||||||
|  |            title: 'gitlab', | ||||||
|  |            created_at: 1.week.ago, | ||||||
|  |            updated_at: 1.week.ago) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   let_it_be(:issue2, reload: true) do | ||||||
|  |     create(:issue, | ||||||
|  |            author: user, | ||||||
|  |            assignees: [user], | ||||||
|  |            project: project2, | ||||||
|  |            description: 'gitlab', | ||||||
|  |            created_at: 1.week.from_now, | ||||||
|  |            updated_at: 1.week.from_now) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   let_it_be(:issue3, reload: true) do | ||||||
|  |     create(:issue, | ||||||
|  |            author: user2, | ||||||
|  |            assignees: [user2], | ||||||
|  |            project: project2, | ||||||
|  |            title: 'tanuki', | ||||||
|  |            description: 'tanuki', | ||||||
|  |            created_at: 2.weeks.from_now, | ||||||
|  |            updated_at: 2.weeks.from_now) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   let_it_be(:issue4, reload: true) { create(:issue, project: project3) } |   let_it_be(:issue4, reload: true) { create(:issue, project: project3) } | ||||||
|  |   let_it_be(:issue5, reload: true) do | ||||||
|  |     create(:issue, | ||||||
|  |            author: user, | ||||||
|  |            assignees: [user], | ||||||
|  |            project: project1, | ||||||
|  |            title: 'wotnot', | ||||||
|  |            created_at: 3.days.ago, | ||||||
|  |            updated_at: 3.days.ago) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   let_it_be(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) } |   let_it_be(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) } | ||||||
|   let_it_be(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) } |   let_it_be(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) } | ||||||
|   let_it_be(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) } |   let_it_be(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue