Commit 869fae6f authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 5688dd2b 451fbf0c
......@@ -90,6 +90,7 @@ export default () => {
labelsFetchPath: $boardApp.dataset.labelsFetchPath,
labelsManagePath: $boardApp.dataset.labelsManagePath,
labelsFilterBasePath: $boardApp.dataset.labelsFilterBasePath,
timeTrackingLimitToHours: parseBoolean($boardApp.dataset.timeTrackingLimitToHours),
},
store,
apolloProvider,
......
......@@ -57,7 +57,6 @@ export default {
:human-time-estimate="store.humanTimeEstimate"
:human-time-spent="store.humanTotalTimeSpent"
:limit-to-hours="store.timeTrackingLimitToHours"
:root-path="store.rootPath"
/>
</div>
</template>
......@@ -44,6 +44,21 @@ export default {
default: false,
required: false,
},
/*
In issue list, "time-tracking-collapsed-state" is always rendered even if the sidebar isn't collapsed.
The actual hiding is controlled with css classes:
Hide "time-tracking-collapsed-state"
if .right-sidebar .right-sidebar-collapsed .sidebar-collapsed-icon
Show "time-tracking-collapsed-state"
if .right-sidebar .right-sidebar-expanded .sidebar-collapsed-icon
In Swimlanes sidebar, we do not use collapsed state at all.
*/
showCollapsed: {
type: Boolean,
default: true,
required: false,
},
},
data() {
return {
......@@ -93,8 +108,9 @@ export default {
</script>
<template>
<div v-cloak class="time_tracker time-tracking-component-wrap">
<div v-cloak class="time-tracker time-tracking-component-wrap" data-testid="time-tracker">
<time-tracking-collapsed-state
v-if="showCollapsed"
:show-comparison-state="showComparisonState"
:show-no-time-tracking-state="showNoTimeTrackingState"
:show-help-state="showHelpState"
......@@ -103,7 +119,7 @@ export default {
:time-spent-human-readable="humanTimeSpent"
:time-estimate-human-readable="humanTimeEstimate"
/>
<div class="title hide-collapsed">
<div class="title hide-collapsed gl-mb-3">
{{ __('Time tracking') }}
<div
v-if="!showHelpState"
......
......@@ -808,11 +808,7 @@
}
}
.time_tracker {
padding-bottom: 0;
border-bottom: 0;
.time-tracker {
.sidebar-collapsed-icon {
> .stopwatch-svg {
display: inline-block;
......
# frozen_string_literal: true
class AddEsCustomAnalyzersSettings < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column :application_settings, :elasticsearch_analyzers_smartcn_enabled, :bool, null: false, default: false
add_column :application_settings, :elasticsearch_analyzers_smartcn_search, :bool, null: false, default: false
add_column :application_settings, :elasticsearch_analyzers_kuromoji_enabled, :bool, null: false, default: false
add_column :application_settings, :elasticsearch_analyzers_kuromoji_search, :bool, null: false, default: false
end
end
1bd99d7d6b972ea66495f21358e3b8731532219fcf75731bf643c312eb56820d
\ No newline at end of file
......@@ -9297,6 +9297,10 @@ CREATE TABLE application_settings (
encrypted_ci_jwt_signing_key text,
encrypted_ci_jwt_signing_key_iv text,
container_registry_expiration_policies_worker_capacity integer DEFAULT 0 NOT NULL,
elasticsearch_analyzers_smartcn_enabled boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_smartcn_search boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_kuromoji_enabled boolean DEFAULT false NOT NULL,
elasticsearch_analyzers_kuromoji_search boolean DEFAULT false NOT NULL,
secret_detection_token_revocation_enabled boolean DEFAULT false NOT NULL,
secret_detection_token_revocation_url text,
encrypted_secret_detection_token_revocation_token text,
......
......@@ -1273,6 +1273,11 @@ tested for within the unit test of `Types::MutationType`. The merge request
can be referred to as an example of this, including the method of testing
deprecated aliased mutations.
#### Deprecating EE mutations
EE mutations should follow the same process. For an example of the merge request
process, read [merge request !42588](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42588).
## Pagination implementation
To learn more, visit [GraphQL pagination](graphql_guide/pagination.md).
......
......@@ -426,6 +426,38 @@ module EE
end
```
### Code in `app/graphql/`
EE-specific mutations, resolvers, and types should be added to
`ee/app/graphql/{mutations,resolvers,types}`.
To override a CE mutation, resolver, or type, create the file in
`ee/app/graphql/ee/{mutations,resolvers,types}` and add new code to a
`prepended` block.
For example, if CE has a mutation called `Mutations::Tanukis::Create` and you
wanted to add a new argument, place the EE override in
`ee/app/graphql/ee/mutations/tanukis/create.rb`:
```ruby
module EE
module Mutations
module Tanukis
module Create
extend ActiveSupport::Concern
prepended do
argument :name,
GraphQL::STRING_TYPE,
required: false,
description: 'Tanuki name'
end
end
end
end
end
```
#### Using `render_if_exists`
Instead of using regular `render`, we should use `render_if_exists`, which
......
......@@ -246,6 +246,29 @@ for filtering to work correctly. To do this run the Rake tasks `gitlab:elastic:r
`gitlab:elastic:clear_index_status`. Afterwards, removing a namespace or a project from the list will delete the data
from the Elasticsearch index as expected.
## Enabling custom language analyzers
You can improve the language support for Chinese and Japanese languages by utilizing [smartcn](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html) and/or [kuromoji](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) analysis plugins from Elastic.
To enable language(s) support:
1. Install the desired plugin(s), please refer to [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/plugins/7.9/installation.html) for plugins installation instructions. The plugin(s) must be installed on every node in the cluster, and each node must be restarted after installation. For a list of plugins, see the table later in this section.
1. Navigate to the **Admin Area** (wrench icon), then **Settings > General**..
1. Expand the **Advanced Search** section and locate **Custom analyzers: language support**.
1. Enable plugin(s) support for **Indexing**.
1. Click **Save changes** for the changes to take effect.
1. Trigger [Zero downtime reindexing](#zero-downtime-reindexing) or reindex everything from scratch to create a new index with updated mappings.
1. Enable plugin(s) support for **Searching** after the previous step is completed.
For guidance on what to install, see the following Elasticsearch language plugin options:
| Parameter | Description |
|-------------------------------------------------------|-------------|
| `Enable Chinese (smartcn) custom analyzer: Indexing` | Enables or disables Chinese language support using [smartcn](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html) custom analyzer for newly created indices.|
| `Enable Chinese (smartcn) custom analyzer: Search` | Enables or disables using [smartcn](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html) fields for Advanced Search. Please only enable this after [installing the plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html), enabling custom analyzer indexing and recreating the index.|
| `Enable Japanese (kuromoji) custom analyzer: Indexing` | Enables or disables Japanese language support using [kuromoji](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) custom analyzer for newly created indices.|
| `Enable Japanese (kuromoji) custom analyzer: Search` | Enables or disables using [kuromoji](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) fields for Advanced Search. Please only enable this after [installing the plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html), enabling custom analyzer indexing and recreating the index.|
## Disabling Advanced Search
To disable the Elasticsearch integration:
......
......@@ -7,6 +7,7 @@ import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees
import IssuableTitle from '~/boards/components/issuable_title.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import BoardSidebarEpicSelect from './sidebar/board_sidebar_epic_select.vue';
import BoardSidebarTimeTracker from './sidebar/board_sidebar_time_tracker.vue';
import BoardSidebarWeightInput from './sidebar/board_sidebar_weight_input.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
......@@ -17,6 +18,7 @@ export default {
GlDrawer,
IssuableTitle,
BoardSidebarEpicSelect,
BoardSidebarTimeTracker,
BoardSidebarWeightInput,
BoardSidebarLabelsSelect,
},
......@@ -48,6 +50,7 @@ export default {
<template>
<issuable-assignees :users="getActiveIssue.assignees" />
<board-sidebar-epic-select />
<board-sidebar-time-tracker class="swimlanes-sidebar-time-tracker" />
<board-sidebar-weight-input v-if="glFeatures.issueWeights" />
<board-sidebar-labels-select />
</template>
......
<script>
import { mapGetters } from 'vuex';
import IssuableTimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
export default {
inject: ['timeTrackingLimitToHours'],
components: {
IssuableTimeTracker,
},
computed: {
...mapGetters(['getActiveIssue']),
},
};
</script>
<template>
<issuable-time-tracker
:time-estimate="getActiveIssue.timeEstimate"
:time-spent="getActiveIssue.totalTimeSpent"
:human-time-estimate="getActiveIssue.humanTimeEstimate"
:human-time-spent="getActiveIssue.humanTotalTimeSpent"
:limit-to-hours="timeTrackingLimitToHours"
:show-collapsed="false"
/>
</template>
......@@ -7,6 +7,9 @@ fragment IssueNode on Issue {
referencePath: reference(full: true)
dueDate
timeEstimate
totalTimeSpent
humanTimeEstimate
humanTotalTimeSpent
weight
confidential
webUrl
......
......@@ -119,3 +119,15 @@ $epic-icons-spacing: 40px;
max-width: calc(100vw - #{$contextual-sidebar-width} - #{$gutter-width} - #{$epic-icons-spacing});
}
}
.swimlanes-sidebar-time-tracker {
.time-tracking-help-state {
margin: 16px -24px 0;
@include gl-px-6;
@include gl-py-2;
@include gl-border-gray-100;
@include gl-border-t-solid;
@include gl-border-t-1;
@include gl-border-b-0;
}
}
......@@ -40,6 +40,10 @@ module EE
:elasticsearch_namespace_ids,
:elasticsearch_project_ids,
:elasticsearch_client_request_timeout,
:elasticsearch_analyzers_smartcn_enabled,
:elasticsearch_analyzers_smartcn_search,
:elasticsearch_analyzers_kuromoji_enabled,
:elasticsearch_analyzers_kuromoji_search,
:enforce_namespace_storage_limit,
:geo_status_timeout,
:geo_node_allowed_ips,
......
......@@ -4,7 +4,7 @@ module Geo
module BlobReplicatorStrategy
extend ActiveSupport::Concern
include Delay
include ::Geo::VerifiableReplicator
include Gitlab::Geo::LogHelpers
included do
......@@ -17,7 +17,8 @@ module Geo
return unless self.class.enabled?
publish(:created, **created_params)
schedule_checksum_calculation if needs_checksum?
after_verifiable_update
end
# Called by Gitlab::Geo::Replicator#consume
......@@ -47,45 +48,8 @@ module Geo
carrierwave_uploader.path
end
def calculate_checksum!
checksum = model_record.calculate_checksum!
update_verification_state!(checksum: checksum)
rescue => e
log_error('Error calculating the checksum', e)
update_verification_state!(failure: e.message)
end
# Check if given checksum matches known one
#
# @param [String] checksum
# @return [Boolean] whether checksum matches
def matches_checksum?(checksum)
model_record.verification_checksum == checksum
end
private
# Update checksum on Geo primary database
#
# @param [String] checksum value generated by the checksum routine
# @param [String] failure (optional) stacktrace from failed execution
def update_verification_state!(checksum: nil, failure: nil)
retry_at, retry_count = calculate_next_retry_attempt if failure.present?
model_record.update!(
verification_checksum: checksum,
verified_at: Time.current,
verification_failure: failure,
verification_retry_at: retry_at,
verification_retry_count: retry_count
)
end
def calculate_next_retry_attempt
retry_count = model_record.verification_retry_count.to_i + 1
[next_retry_time(retry_count), retry_count]
end
def download
::Geo::BlobDownloadService.new(replicator: self).execute
end
......
......@@ -4,7 +4,7 @@ module Geo
module RepositoryReplicatorStrategy
extend ActiveSupport::Concern
include Delay
include ::Geo::VerifiableReplicator
include Gitlab::Geo::LogHelpers
included do
......
# frozen_string_literal: true
module Geo
module VerifiableReplicator
extend ActiveSupport::Concern
include Delay
class_methods do
def checksummed
model.available_replicables.checksummed
end
def checksummed_count
model.available_replicables.checksummed.count
end
def checksum_failed_count
model.available_replicables.checksum_failed.count
end
end
def after_verifiable_update
schedule_checksum_calculation if needs_checksum?
end
def calculate_checksum!
checksum = model_record.calculate_checksum!
update_verification_state!(checksum: checksum)
rescue => e
log_error('Error calculating the checksum', e)
update_verification_state!(failure: e.message)
end
# Check if given checksum matches known one
#
# @param [String] checksum
# @return [Boolean] whether checksum matches
def matches_checksum?(checksum)
model_record.verification_checksum == checksum
end
def needs_checksum?
return true unless model_record.respond_to?(:needs_checksum?)
model_record.needs_checksum?
end
# Checksum value from the main database
#
# @abstract
def primary_checksum
model_record.verification_checksum
end
def secondary_checksum
registry.verification_checksum
end
private
# Update checksum on Geo primary database
#
# @param [String] checksum value generated by the checksum routine
# @param [String] failure (optional) stacktrace from failed execution
def update_verification_state!(checksum: nil, failure: nil)
retry_at, retry_count = calculate_next_retry_attempt if failure.present?
model_record.update!(
verification_checksum: checksum,
verified_at: Time.current,
verification_failure: failure,
verification_retry_at: retry_at,
verification_retry_count: retry_count
)
end
def calculate_next_retry_attempt
retry_count = model_record.verification_retry_count.to_i + 1
[next_retry_time(retry_count), retry_count]
end
def schedule_checksum_calculation
raise NotImplementedError
end
end
end
......@@ -119,6 +119,10 @@ module EE
elasticsearch_shards: 5,
elasticsearch_url: ENV['ELASTIC_URL'] || 'http://localhost:9200',
elasticsearch_client_request_timeout: 0,
elasticsearch_analyzers_smartcn_enabled: false,
elasticsearch_analyzers_smartcn_search: false,
elasticsearch_analyzers_kuromoji_enabled: false,
elasticsearch_analyzers_kuromoji_search: false,
email_additional_text: nil,
enforce_namespace_storage_limit: false,
enforce_pat_expiration: true,
......
......@@ -148,6 +148,41 @@
- else
= f.text_field :elasticsearch_project_ids, class: 'js-elasticsearch-projects', value: elasticsearch_project_ids, data: { selected: elasticsearch_objects_options(@application_setting.elasticsearch_limited_projects(true)).to_json }
.sub-section
%h4= _('Custom analyzers: language support')
%h5
= _('Chinese language support using')
%a{ href: 'https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html' }
= _('smartcn custom analyzer')
.form-group
.form-check
= f.check_box :elasticsearch_analyzers_smartcn_enabled, class: 'form-check-input'
= f.label :elasticsearch_analyzers_smartcn_enabled, class: 'form-check-label' do
= _('Enable smartcn custom analyzer: Indexing')
.form-group
.form-check
= f.check_box :elasticsearch_analyzers_smartcn_search, class: 'form-check-input', disabled: !Gitlab::CurrentSettings.elasticsearch_analyzers_smartcn_enabled?
= f.label :elasticsearch_analyzers_smartcn_search, class: 'form-check-label' do
= _('Enable smartcn custom analyzer: Search')
.form-text.gl-text-gray-600
= _('Please only enable search after installing the plugin, enabling indexing and recreating the index')
%h5
= _('Japanese language support using')
%a{ href: 'https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html' }
= _('kuromoji custom analyzer')
.form-group
.form-check
= f.check_box :elasticsearch_analyzers_kuromoji_enabled, class: 'form-check-input'
= f.label :elasticsearch_analyzers_kuromoji_enabled, class: 'form-check-label' do
= _('Enable kuromoji custom analyzer: Indexing')
.form-group
.form-check
= f.check_box :elasticsearch_analyzers_kuromoji_search, class: 'form-check-input', disabled: !Gitlab::CurrentSettings.elasticsearch_analyzers_kuromoji_enabled?
= f.label :elasticsearch_analyzers_kuromoji_search, class: 'form-check-label' do
= _('Enable kuromoji custom analyzer: Search')
.form-text.gl-text-gray-600
= _('Please only enable search after installing the plugin, enabling indexing and recreating the index')
.sub-section
%h4= _('Elasticsearch AWS IAM credentials')
.form-group
......
---
title: 'Advanced Search: Add optional Chinese and Japanese languages support'
merge_request: 45513
author:
type: added
......@@ -55,6 +55,8 @@ module Elastic
end
def basic_query_hash(fields, query)
fields = CustomLanguageAnalyzers.add_custom_analyzers_fields(fields)
query_hash =
if query.present?
{
......
# frozen_string_literal: true
module Elastic
module Latest
module CustomLanguageAnalyzers
class << self
SUPPORTED_FIELDS = %i{title description}.freeze
def custom_analyzers_mappings(type: :text)
hash = { doc: { properties: {} } }
SUPPORTED_FIELDS.each do |field|
hash[:doc][:properties][field] = {
fields: custom_analyzers_fields(type: type)
}
end
hash
end
def custom_analyzers_fields(type:)
custom_analyzers_enabled.each_with_object({}) do |analyzer, hash|
hash[analyzer.to_sym] = {
analyzer: analyzer,
type: type
}
end
end
def add_custom_analyzers_fields(fields)
search_analyzers = custom_analyzers_search
return fields if search_analyzers.blank?
fields_names = fields.map { |m| m[/\w+/] }
SUPPORTED_FIELDS.each do |field|
next unless fields_names.include?(field.to_s)
search_analyzers.each do |analyzer|
fields << "#{field}.#{analyzer}"
end
end
fields
end
private
def custom_analyzers_enabled
[].tap do |enabled|
enabled << 'smartcn' if ::Gitlab::CurrentSettings.elasticsearch_analyzers_smartcn_enabled
enabled << 'kuromoji' if ::Gitlab::CurrentSettings.elasticsearch_analyzers_kuromoji_enabled
end
end
def custom_analyzers_search
enabled_analyzers = custom_analyzers_enabled
[].tap do |analyzers|
analyzers << 'smartcn' if enabled_analyzers.include?('smartcn') && ::Gitlab::CurrentSettings.elasticsearch_analyzers_smartcn_search
analyzers << 'kuromoji' if enabled_analyzers.include?('kuromoji') && ::Gitlab::CurrentSettings.elasticsearch_analyzers_kuromoji_search
end
end
end
end
end
end
......@@ -3,6 +3,17 @@
module Gitlab
module Elastic
class Helper
ES_ENABLED_CLASSES = [
Project,
Issue,
MergeRequest,
Snippet,
Note,
Milestone,
ProjectWiki,
Repository
].freeze
attr_reader :version, :client
attr_accessor :target_name
......@@ -28,6 +39,19 @@ module Gitlab
end
end
def default_settings
ES_ENABLED_CLASSES.inject({}) do |settings, klass|
settings.deep_merge(klass.__elasticsearch__.settings.to_hash)
end
end
def default_mappings
mappings = ES_ENABLED_CLASSES.inject({}) do |m, klass|
m.deep_merge(klass.__elasticsearch__.mappings.to_hash)
end
mappings.deep_merge(::Elastic::Latest::CustomLanguageAnalyzers.custom_analyzers_mappings)
end
def create_empty_index(with_alias: true, options: {})
new_index_name = options[:index_name] || "#{target_name}-#{Time.now.strftime("%Y%m%d-%H%M")}"
......@@ -35,24 +59,10 @@ module Gitlab
raise "Index under '#{with_alias ? target_name : new_index_name}' already exists, use `recreate_index` to recreate it."
end
settings = {}
mappings = {}
[
Project,
Issue,
MergeRequest,
Snippet,
Note,
Milestone,
ProjectWiki,
Repository
].each do |klass|
settings.deep_merge!(klass.__elasticsearch__.settings.to_hash)
mappings.deep_merge!(klass.__elasticsearch__.mappings.to_hash)
end
settings = default_settings
settings.merge!(options[:settings]) if options[:settings]
mappings = default_mappings
mappings.merge!(options[:mappings]) if options[:mappings]
create_index_options = {
......
......@@ -134,18 +134,6 @@ module Gitlab
replicator_class.new(model_record_id: replicable_id)
end
def self.checksummed
model.available_replicables.checksummed
end
def self.checksummed_count
model.available_replicables.checksummed.count
end
def self.checksum_failed_count
model.available_replicables.checksum_failed.count
end
def self.primary_total_count
model.available_replicables.count
end
......@@ -265,17 +253,6 @@ module Gitlab
registry_class.for_model_record_id(model_record_id)
end
# Checksum value from the main database
#
# @abstract
def primary_checksum
model_record.verification_checksum
end
def secondary_checksum
registry.verification_checksum
end
# Return exactly the data needed by `for_replicable_params` to
# reinstantiate this Replicator elsewhere.
#
......@@ -298,10 +275,6 @@ module Gitlab
publish(:updated, **updated_params)
end
def schedule_checksum_calculation
raise NotImplementedError
end
def created_params
event_params
end
......@@ -318,12 +291,6 @@ module Gitlab
{ model_record_id: model_record.id }
end
def needs_checksum?
return true unless model_record.respond_to?(:needs_checksum?)
model_record.needs_checksum?
end
protected
# Store an event on the database
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'epics swimlanes sidebar', :js do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:board) { create(:board, project: project) }
let_it_be(:list) { create(:list, board: board, position: 0) }
let_it_be(:epic1) { create(:epic, group: group) }
let_it_be(:issue1, reload: true) { create(:issue, project: project) }
let_it_be(:epic_issue1, reload: true) { create(:epic_issue, epic: epic1, issue: issue1) }
before do
project.add_maintainer(user)
stub_licensed_features(epics: true, swimlanes: true)
sign_in(user)
visit project_boards_path(project)
wait_for_requests
page.within('.board-swimlanes-toggle-wrapper') do
page.find('.dropdown-toggle').click
page.find('.dropdown-item', text: 'Epic').click
end
wait_for_all_requests
end
context 'time tracking' do
it 'displays time tracking feature with default message' do
click_first_issue_card
page.within('[data-testid="time-tracker"]') do
expect(page).to have_content('Time tracking')
expect(page).to have_content('No estimate or time spent')
end
end
context 'when only spent time is recorded' do
before do
issue1.timelogs.create!(time_spent: 3600, user: user)
click_first_issue_card
end
it 'shows the total time spent only' do
page.within('[data-testid="time-tracker"]') do
expect(page).to have_content('Spent: 1h')
expect(page).not_to have_content('Estimated')
end
end
end
context 'when only estimated time is recorded' do
before do
issue1.update!(time_estimate: 3600)
click_first_issue_card
end
it 'shows the estimated time only' do
page.within('[data-testid="time-tracker"]') do
expect(page).to have_content('Estimated: 1h')
expect(page).not_to have_content('Spent')
end
end
end
context 'when estimated and spent times are available' do
before do
issue1.update!(time_estimate: 3600)
issue1.timelogs.create!(time_spent: 1800, user: user)
click_first_issue_card
end
it 'shows time tracking progress bar' do
page.within('[data-testid="time-tracker"]') do
expect(page).to have_selector('[data-testid="timeTrackingComparisonPane"]')
end
end
it 'shows both estimated and spent time text' do
page.within('[data-testid="time-tracker"]') do
expect(page).to have_content('Spent 30m')
expect(page).to have_content('Est 1h')
end
end
end
context 'when limitedToHours instance option is turned on' do
before do
stub_application_setting(time_tracking_limit_to_hours: true)
# 3600+3600*24 = 1d 1h or 25h
issue1.timelogs.create!(time_spent: 3600 + 3600 * 24, user: user)
click_first_issue_card
end
it 'shows the total time spent only' do
page.within('[data-testid="time-tracker"]') do
expect(page).to have_content('Spent: 25h')
end
end
end
end
def click_first_issue_card
page.within("[data-testid='board-epic-lane-issues']") do
first("[data-testid='board_card']").click
end
end
end
......@@ -19,6 +19,7 @@ describe('ee/BoardContentSidebar', () => {
store,
stubs: {
'board-sidebar-epic-select': '<div></div>',
'board-sidebar-time-tracker': '<div></div>',
'board-sidebar-weight-input': '<div></div>',
'board-sidebar-labels-select': '<div></div>',
},
......
......@@ -11,6 +11,9 @@ describe('ee/BoardContent', () => {
const createComponent = () => {
wrapper = shallowMount(BoardContent, {
store,
provide: {
timeTrackingLimitToHours: false,
},
propsData: {
lists: [],
canAdminList: false,
......
/*
To avoid duplicating tests in time_tracker.spec,
this spec only contains a simple test to check rendering.
A detailed feature spec is used to test time tracking feature
in swimlanes sidebar.
*/
import { shallowMount } from '@vue/test-utils';
import BoardSidebarTimeTracker from 'ee/boards/components/sidebar/board_sidebar_time_tracker.vue';
import IssuableTimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
import { createStore } from '~/boards/stores';
describe('BoardSidebarTimeTracker', () => {
let wrapper;
let store;
const createComponent = options => {
wrapper = shallowMount(BoardSidebarTimeTracker, {
store,
...options,
});
};
beforeEach(() => {
store = createStore();
store.state.issues = {
'1': {
timeEstimate: 3600,
totalTimeSpent: 1800,
humanTimeEstimate: '1h',
humanTotalTimeSpent: '30min',
},
};
store.state.activeId = '1';
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it.each([[true], [false]])(
'renders IssuableTimeTracker with correct spent and estimated time (timeTrackingLimitToHours=%s)',
timeTrackingLimitToHours => {
createComponent({ provide: { timeTrackingLimitToHours } });
expect(wrapper.find(IssuableTimeTracker).props()).toEqual({
timeEstimate: 3600,
timeSpent: 1800,
humanTimeEstimate: '1h',
humanTimeSpent: '30min',
limitToHours: timeTrackingLimitToHours,
showCollapsed: false,
});
},
);
});
......@@ -39,6 +39,20 @@ RSpec.describe Gitlab::Elastic::Helper do
end
end
describe '#default_mappings' do
context 'custom analyzers' do
let(:custom_analyzers_mappings) { { doc: { properties: { title: { fields: { custom: true } } } } } }
before do
allow(::Elastic::Latest::CustomLanguageAnalyzers).to receive(:custom_analyzers_mappings).and_return(custom_analyzers_mappings)
end
it 'merges custom language analyzers mappings' do
expect(helper.default_mappings[:doc][:properties][:title]).to include(custom_analyzers_mappings[:doc][:properties][:title])
end
end
end
describe '#create_empty_index' do
context 'with an empty cluster' do
context 'with alias and index' do
......
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rspec-parameterized'
RSpec.describe Elastic::Latest::CustomLanguageAnalyzers do
describe '.custom_analyzers_mappings' do
before do
allow(::Gitlab::CurrentSettings).to receive(:elasticsearch_analyzers_smartcn_enabled).and_return(true)
allow(::Gitlab::CurrentSettings).to receive(:elasticsearch_analyzers_kuromoji_enabled).and_return(true)
end
it 'returns correct structure' do
expect(described_class.custom_analyzers_mappings).to eq(
{
doc: {
properties: {
title: {
fields: described_class.custom_analyzers_fields(type: :text)
},
description: {
fields: described_class.custom_analyzers_fields(type: :text)
}
}
}
}
)
end
end
describe '.custom_analyzers_fields' do
using RSpec::Parameterized::TableSyntax
where(:smartcn_enabled, :kuromoji_enabled, :expected_result) do
false | false | {}
true | false | { smartcn: { analyzer: 'smartcn', type: :text } }
false | true | { kuromoji: { analyzer: 'kuromoji', type: :text } }
true | true | { smartcn: { analyzer: 'smartcn', type: :text }, kuromoji: { analyzer: 'kuromoji', type: :text } }
end
with_them do
before do
allow(::Gitlab::CurrentSettings).to receive(:elasticsearch_analyzers_smartcn_enabled).and_return(smartcn_enabled)
allow(::Gitlab::CurrentSettings).to receive(:elasticsearch_analyzers_kuromoji_enabled).and_return(kuromoji_enabled)
end
it 'returns correct config' do
expect(described_class.custom_analyzers_fields(type: :text)).to eq(expected_result)
end
end
end
describe '.add_custom_analyzers_fields' do
using RSpec::Parameterized::TableSyntax
let!(:original_fields) { %w(title^2 confidential).freeze }
where(:smartcn_enabled, :kuromoji_enabled, :smartcn_search, :kuromoji_search, :expected_additional_fields) do
false | false | false | false | []
false | false | true | true | []
true | true | false | false | []
true | true | true | false | %w(title.smartcn)
true | true | false | true | %w(title.kuromoji)
true | true | true | true | %w(title.smartcn title.kuromoji)
end
with_them do
before do
allow(::Gitlab::CurrentSettings).to receive(:elasticsearch_analyzers_smartcn_enabled).and_return(smartcn_enabled)
allow(::Gitlab::CurrentSettings).to receive(:elasticsearch_analyzers_kuromoji_enabled).and_return(kuromoji_enabled)
allow(::Gitlab::CurrentSettings).to receive(:elasticsearch_analyzers_smartcn_search).and_return(smartcn_search)
allow(::Gitlab::CurrentSettings).to receive(:elasticsearch_analyzers_kuromoji_search).and_return(kuromoji_search)
end
it 'returns correct fields' do
expect(described_class.add_custom_analyzers_fields(original_fields.dup)).to eq(original_fields + expected_additional_fields)
end
end
end
end
......@@ -19,6 +19,7 @@ RSpec.shared_examples 'a blob replicator' do
end
it_behaves_like 'a replicator'
it_behaves_like 'a verifiable replicator'
# This could be included in each model's spec, but including it here is DRYer.
include_examples 'a replicable model'
......@@ -33,9 +34,8 @@ RSpec.shared_examples 'a blob replicator' do
"replicable_name" => replicator.replicable_name, "event_name" => "created", "payload" => { "model_record_id" => replicator.model_record.id })
end
it 'schedules the checksum calculation if needed' do
expect(Geo::BlobVerificationPrimaryWorker).to receive(:perform_async)
expect(replicator).to receive(:needs_checksum?).and_return(true)
it 'calls #after_verifiable_update' do
expect(replicator).to receive(:after_verifiable_update)
replicator.handle_after_create_commit
end
......@@ -45,8 +45,8 @@ RSpec.shared_examples 'a blob replicator' do
stub_feature_flags(replicator.replication_enabled_feature_key => false)
end
it 'does not schedule the checksum calculation' do
expect(Geo::BlobVerificationPrimaryWorker).not_to receive(:perform_async)
it 'does not call #after_verifiable_update' do
expect(replicator).not_to receive(:after_verifiable_update)
replicator.handle_after_create_commit
end
......@@ -82,30 +82,6 @@ RSpec.shared_examples 'a blob replicator' do
end
end
describe '#calculate_checksum!' do
it 'calculates the checksum' do
model_record.save!
replicator.calculate_checksum!
expect(model_record.reload.verification_checksum).not_to be_nil
expect(model_record.reload.verified_at).not_to be_nil
end
it 'saves the error message and increments retry counter' do
model_record.save!
allow(model_record).to receive(:calculate_checksum!) do
raise StandardError.new('Failure to calculate checksum')
end
replicator.calculate_checksum!
expect(model_record.reload.verification_failure).to eq 'Failure to calculate checksum'
expect(model_record.verification_retry_count).to be 1
end
end
describe 'created event consumption' do
context "when the blob's project is in replicables for this geo node" do
it 'invokes Geo::BlobDownloadService' do
......
# frozen_string_literal: true
# This is included by BlobReplicatorStrategy and RepositoryReplicatorStrategy.
#
RSpec.shared_examples 'a verifiable replicator' do
include EE::GeoHelpers
describe '#after_verifiable_update' do
it 'schedules the checksum calculation if needed' do
expect(replicator).to receive(:schedule_checksum_calculation)
expect(replicator).to receive(:needs_checksum?).and_return(true)
replicator.after_verifiable_update
end
end
describe '#calculate_checksum!' do
it 'calculates the checksum' do
model_record.save!
replicator.calculate_checksum!
expect(model_record.reload.verification_checksum).not_to be_nil
expect(model_record.reload.verified_at).not_to be_nil
end
it 'saves the error message and increments retry counter' do
model_record.save!
allow(model_record).to receive(:calculate_checksum!) do
raise StandardError.new('Failure to calculate checksum')
end
replicator.calculate_checksum!
expect(model_record.reload.verification_failure).to eq 'Failure to calculate checksum'
expect(model_record.verification_retry_count).to be 1
end
end
end
......@@ -5164,6 +5164,9 @@ msgstr ""
msgid "Child epic doesn't exist."
msgstr ""
msgid "Chinese language support using"
msgstr ""
msgid "Choose %{strong_open}Create archive%{strong_close} and wait for archiving to complete."
msgstr ""
......@@ -7910,6 +7913,9 @@ msgstr ""
msgid "Custom Git clone URL for HTTP(S)"
msgstr ""
msgid "Custom analyzers: language support"
msgstr ""
msgid "Custom hostname (for private commit emails)"
msgstr ""
......@@ -9924,6 +9930,12 @@ msgstr ""
msgid "Enable integration"
msgstr ""
msgid "Enable kuromoji custom analyzer: Indexing"
msgstr ""
msgid "Enable kuromoji custom analyzer: Search"
msgstr ""
msgid "Enable maintenance mode"
msgstr ""
......@@ -9960,6 +9972,12 @@ msgstr ""
msgid "Enable shared runners for this group"
msgstr ""
msgid "Enable smartcn custom analyzer: Indexing"
msgstr ""
msgid "Enable smartcn custom analyzer: Search"
msgstr ""
msgid "Enable snowplow tracking"
msgstr ""
......@@ -14892,6 +14910,9 @@ msgstr ""
msgid "January"
msgstr ""
msgid "Japanese language support using"
msgstr ""
msgid "Jira Issues"
msgstr ""
......@@ -19795,6 +19816,9 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
msgid "Please only enable search after installing the plugin, enabling indexing and recreating the index"
msgstr ""
msgid "Please provide a name"
msgstr ""
......@@ -31586,6 +31610,9 @@ msgstr ""
msgid "jigsaw is not defined"
msgstr ""
msgid "kuromoji custom analyzer"
msgstr ""
msgid "last commit:"
msgstr ""
......@@ -32213,6 +32240,9 @@ msgstr ""
msgid "sign in"
msgstr ""
msgid "smartcn custom analyzer"
msgstr ""
msgid "sort:"
msgstr ""
......
......@@ -16,7 +16,6 @@ describe('Issuable Time Tracker', () => {
humanTimeEstimate: '2h 46m',
humanTimeSpent: '1h 23m',
limitToHours: false,
rootPath: '/',
};
const mountComponent = ({ props = {} } = {}) =>
......@@ -52,6 +51,24 @@ describe('Issuable Time Tracker', () => {
});
describe('Content panes', () => {
describe('Collapsed state', () => {
it('should render "time-tracking-collapsed-state" by default when "showCollapsed" prop is not specified', () => {
wrapper = mountComponent();
expect(findCollapsedState().exists()).toBe(true);
});
it('should not render "time-tracking-collapsed-state" when "showCollapsed" is false', () => {
wrapper = mountComponent({
props: {
showCollapsed: false,
},
});
expect(findCollapsedState().exists()).toBe(false);
});
});
describe('Comparison pane', () => {
beforeEach(() => {
wrapper = mountComponent({
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment