Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4bc1e04a7a
commit
c3ea5eada6
28 changed files with 473 additions and 54 deletions
|
@ -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,
|
||||
|
|
|
@ -54,7 +54,7 @@ export default {
|
|||
<ide-tree-list>
|
||||
<template #header>
|
||||
{{ __('Edit') }}
|
||||
<div class="ide-tree-actions ml-auto d-flex">
|
||||
<div class="ide-tree-actions ml-auto d-flex" data-testid="ide-root-actions">
|
||||
<new-entry-button
|
||||
:label="__('New file')"
|
||||
:show-label="false"
|
||||
|
|
|
@ -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;
|
||||
|
|
5
changelogs/unreleased/271391-downgrade-vue-router.yml
Normal file
5
changelogs/unreleased/271391-downgrade-vue-router.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix IDE issues with special characters
|
||||
merge_request: 46398
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Show tar warning message when file/folder changed during backup instead of
|
||||
failing whole backup operation
|
||||
merge_request: 42197
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: coverage_fuzzing_mr_widget
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43545
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/257839
|
||||
type: development
|
||||
group: group::fuzz testing
|
||||
default_enabled: false
|
|
@ -0,0 +1,12 @@
|
|||
# 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
|
1
db/schema_migrations/20201019101549
Normal file
1
db/schema_migrations/20201019101549
Normal file
|
@ -0,0 +1 @@
|
|||
1bd99d7d6b972ea66495f21358e3b8731532219fcf75731bf643c312eb56820d
|
|
@ -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,
|
||||
|
|
|
@ -38,7 +38,7 @@ To start multiple processes:
|
|||
process, and values in each item determine the queues it works on.
|
||||
|
||||
For example, the following setting creates three Sidekiq processes, one to run on
|
||||
`elastic_indexer`, one to run on `mailers`, and one process running all on queues:
|
||||
`elastic_indexer`, one to run on `mailers`, and one process running on all queues:
|
||||
|
||||
```ruby
|
||||
sidekiq['queue_groups'] = [
|
||||
|
@ -327,9 +327,9 @@ Running Sidekiq cluster is the default in GitLab 13.0 and later.
|
|||
```
|
||||
|
||||
`min_concurrency` and `max_concurrency` are independent; one can be set without
|
||||
the other. Setting `min_concurrency` to 0 will disable the limit.
|
||||
the other. Setting `min_concurrency` to `0` will disable the limit.
|
||||
|
||||
For each queue group, let N be one more than the number of queues. The
|
||||
For each queue group, let `N` be one more than the number of queues. The
|
||||
concurrency factor will be set to:
|
||||
|
||||
1. `N`, if it's between `min_concurrency` and `max_concurrency`.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -26,7 +26,7 @@ module Backup
|
|||
FileUtils.rm_f(backup_tarball)
|
||||
|
||||
if ENV['STRATEGY'] == 'copy'
|
||||
cmd = [%w(rsync -a), exclude_dirs(:rsync), %W(#{app_files_dir} #{Gitlab.config.backup.path})].flatten
|
||||
cmd = [%w[rsync -a], exclude_dirs(:rsync), %W[#{app_files_dir} #{Gitlab.config.backup.path}]].flatten
|
||||
output, status = Gitlab::Popen.popen(cmd)
|
||||
|
||||
unless status == 0
|
||||
|
@ -34,19 +34,27 @@ module Backup
|
|||
raise Backup::Error, 'Backup failed'
|
||||
end
|
||||
|
||||
tar_cmd = [tar, exclude_dirs(:tar), %W(-C #{@backup_files_dir} -cf - .)].flatten
|
||||
run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
|
||||
tar_cmd = [tar, exclude_dirs(:tar), %W[-C #{@backup_files_dir} -cf - .]].flatten
|
||||
status_list, output = run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
|
||||
FileUtils.rm_rf(@backup_files_dir)
|
||||
else
|
||||
tar_cmd = [tar, exclude_dirs(:tar), %W(-C #{app_files_dir} -cf - .)].flatten
|
||||
run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
|
||||
tar_cmd = [tar, exclude_dirs(:tar), %W[-C #{app_files_dir} -cf - .]].flatten
|
||||
status_list, output = run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
|
||||
end
|
||||
|
||||
unless pipeline_succeeded?(tar_status: status_list[0], gzip_status: status_list[1], output: output)
|
||||
raise Backup::Error, "Backup operation failed: #{output}"
|
||||
end
|
||||
end
|
||||
|
||||
def restore
|
||||
backup_existing_files_dir
|
||||
|
||||
run_pipeline!([%w(gzip -cd), %W(#{tar} --unlink-first --recursive-unlink -C #{app_files_dir} -xf -)], in: backup_tarball)
|
||||
cmd_list = [%w[gzip -cd], %W[#{tar} --unlink-first --recursive-unlink -C #{app_files_dir} -xf -]]
|
||||
status_list, output = run_pipeline!(cmd_list, in: backup_tarball)
|
||||
unless pipeline_succeeded?(gzip_status: status_list[0], tar_status: status_list[1], output: output)
|
||||
raise Backup::Error, "Restore operation failed: #{output}"
|
||||
end
|
||||
end
|
||||
|
||||
def tar
|
||||
|
@ -78,13 +86,44 @@ module Backup
|
|||
def run_pipeline!(cmd_list, options = {})
|
||||
err_r, err_w = IO.pipe
|
||||
options[:err] = err_w
|
||||
status = Open3.pipeline(*cmd_list, options)
|
||||
status_list = Open3.pipeline(*cmd_list, options)
|
||||
err_w.close
|
||||
return if status.compact.all?(&:success?)
|
||||
|
||||
regex = /^g?tar: \.: Cannot mkdir: No such file or directory$/
|
||||
error = err_r.read
|
||||
raise Backup::Error, "Backup failed. #{error}" unless error =~ regex
|
||||
[status_list, err_r.read]
|
||||
end
|
||||
|
||||
def noncritical_warning?(warning)
|
||||
noncritical_warnings = [
|
||||
/^g?tar: \.: Cannot mkdir: No such file or directory$/
|
||||
]
|
||||
|
||||
noncritical_warnings.map { |w| warning =~ w }.any?
|
||||
end
|
||||
|
||||
def pipeline_succeeded?(tar_status:, gzip_status:, output:)
|
||||
return false unless gzip_status&.success?
|
||||
|
||||
tar_status&.success? || tar_ignore_non_success?(tar_status.exitstatus, output)
|
||||
end
|
||||
|
||||
def tar_ignore_non_success?(exitstatus, output)
|
||||
# tar can exit with nonzero code:
|
||||
# 1 - if some files changed (i.e. a CI job is currently writes to log)
|
||||
# 2 - if it cannot create `.` directory (see issue https://gitlab.com/gitlab-org/gitlab/-/issues/22442)
|
||||
# http://www.gnu.org/software/tar/manual/html_section/tar_19.html#Synopsis
|
||||
# so check tar status 1 or stderr output against some non-critical warnings
|
||||
if exitstatus == 1
|
||||
$stdout.puts "Ignoring tar exit status 1 'Some files differ': #{output}"
|
||||
return true
|
||||
end
|
||||
|
||||
# allow tar to fail with other non-success status if output contain non-critical warning
|
||||
if noncritical_warning?(output)
|
||||
$stdout.puts "Ignoring non-success exit status #{exitstatus} due to output of non-critical warning(s): #{output}"
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def exclude_dirs(fmt)
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
@ -31139,9 +31163,6 @@ msgstr ""
|
|||
msgid "ciReport|Coverage Fuzzing"
|
||||
msgstr ""
|
||||
|
||||
msgid "ciReport|Coverage Fuzzing Title"
|
||||
msgstr ""
|
||||
|
||||
msgid "ciReport|Coverage fuzzing"
|
||||
msgstr ""
|
||||
|
||||
|
@ -31589,6 +31610,9 @@ msgstr ""
|
|||
msgid "jigsaw is not defined"
|
||||
msgstr ""
|
||||
|
||||
msgid "kuromoji custom analyzer"
|
||||
msgstr ""
|
||||
|
||||
msgid "last commit:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -32216,6 +32240,9 @@ msgstr ""
|
|||
msgid "sign in"
|
||||
msgstr ""
|
||||
|
||||
msgid "smartcn custom analyzer"
|
||||
msgstr ""
|
||||
|
||||
msgid "sort:"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@
|
|||
"vue": "^2.6.12",
|
||||
"vue-apollo": "^3.0.3",
|
||||
"vue-loader": "^15.9.3",
|
||||
"vue-router": "^3.4.7",
|
||||
"vue-router": "3.4.5",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"vue-virtual-scroll-list": "^1.4.4",
|
||||
"vuedraggable": "^2.23.0",
|
||||
|
|
|
@ -105,7 +105,8 @@ describe('Monitoring router', () => {
|
|||
path | currentDashboard
|
||||
${'/panel/new'} | ${undefined}
|
||||
${'/dashboard.yml/panel/new'} | ${'dashboard.yml'}
|
||||
${'/config%2Fprometheus%2Fcommon_metrics.yml/panel/new'} | ${'config%2Fprometheus%2Fcommon_metrics.yml'}
|
||||
${'/config/prometheus/common_metrics.yml/panel/new'} | ${'config/prometheus/common_metrics.yml'}
|
||||
${'/config%2Fprometheus%2Fcommon_metrics.yml/panel/new'} | ${'config/prometheus/common_metrics.yml'}
|
||||
`('"$path" renders page with dashboard "$currentDashboard"', ({ path, currentDashboard }) => {
|
||||
const wrapper = createWrapper(BASE_PATH, path);
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { findAllByText, fireEvent, getByLabelText, screen } from '@testing-library/dom';
|
||||
|
||||
const isFileRowOpen = row => row.matches('.is-open');
|
||||
const isFolderRowOpen = row => row.matches('.folder.is-open');
|
||||
|
||||
const getLeftSidebar = () => screen.getByTestId('left-sidebar');
|
||||
|
||||
|
@ -24,6 +24,8 @@ const findAndSetEditorValue = async value => {
|
|||
|
||||
const findTreeBody = () => screen.findByTestId('ide-tree-body', {}, { timeout: 5000 });
|
||||
|
||||
const findRootActions = () => screen.findByTestId('ide-root-actions', {}, { timeout: 7000 });
|
||||
|
||||
const findFileRowContainer = (row = null) =>
|
||||
row ? Promise.resolve(row.parentElement) : findTreeBody();
|
||||
|
||||
|
@ -35,7 +37,7 @@ const findFileChild = async (row, name, index = 0) => {
|
|||
};
|
||||
|
||||
const openFileRow = row => {
|
||||
if (!row || isFileRowOpen(row)) {
|
||||
if (!row || isFolderRowOpen(row)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -74,6 +76,19 @@ const findAndSetFileName = async value => {
|
|||
createButton.click();
|
||||
};
|
||||
|
||||
const findAndClickRootAction = async name => {
|
||||
const container = await findRootActions();
|
||||
const button = getByLabelText(container, name);
|
||||
|
||||
button.click();
|
||||
};
|
||||
|
||||
export const openFile = async path => {
|
||||
const row = await findAndTraverseToPath(path);
|
||||
|
||||
openFileRow(row);
|
||||
};
|
||||
|
||||
export const createFile = async (path, content) => {
|
||||
const parentPath = path
|
||||
.split('/')
|
||||
|
@ -81,7 +96,12 @@ export const createFile = async (path, content) => {
|
|||
.join('/');
|
||||
|
||||
const parentRow = await findAndTraverseToPath(parentPath);
|
||||
clickFileRowAction(parentRow, 'New file');
|
||||
|
||||
if (parentRow) {
|
||||
clickFileRowAction(parentRow, 'New file');
|
||||
} else {
|
||||
await findAndClickRootAction('New file');
|
||||
}
|
||||
|
||||
await findAndSetFileName(path);
|
||||
await findAndSetEditorValue(content);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import { waitForText } from 'helpers/wait_for_text';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
|
||||
import { createCommitId } from 'test_helpers/factories/commit_id';
|
||||
import { initIde } from '~/ide';
|
||||
|
@ -86,4 +87,18 @@ describe('WebIDE', () => {
|
|||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('user adds file that starts with +', async () => {
|
||||
createComponent();
|
||||
|
||||
await ideHelper.createFile('+test', 'Hello world!');
|
||||
await ideHelper.openFile('+test');
|
||||
|
||||
// Wait for monaco things
|
||||
await waitForPromises();
|
||||
|
||||
// Assert that +test is the only open tab
|
||||
const tabs = Array.from(document.querySelectorAll('.multi-file-tab'));
|
||||
expect(tabs.map(x => x.textContent.trim())).toEqual(['+test']);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ beforeEach(() => {
|
|||
relative_url_root: '',
|
||||
};
|
||||
|
||||
setTestTimeout(5000);
|
||||
setTestTimeout(7000);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@ RSpec.describe Backup::Artifacts do
|
|||
|
||||
it 'excludes tmp from backup tar' do
|
||||
expect(backup).to receive(:tar).and_return('blabla-tar')
|
||||
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/gitlab-artifacts -cf - .), 'gzip -c -1'], any_args)
|
||||
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/gitlab-artifacts -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
||||
expect(backup).to receive(:pipeline_succeeded?).and_return(true)
|
||||
backup.dump
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,10 @@ RSpec.describe Backup::Files do
|
|||
let(:progress) { StringIO.new }
|
||||
let!(:project) { create(:project) }
|
||||
|
||||
let(:status_0) { double('exit 0', success?: true, exitstatus: 0) }
|
||||
let(:status_1) { double('exit 1', success?: false, exitstatus: 1) }
|
||||
let(:status_2) { double('exit 2', success?: false, exitstatus: 2) }
|
||||
|
||||
before do
|
||||
allow(progress).to receive(:puts)
|
||||
allow(progress).to receive(:print)
|
||||
|
@ -24,6 +28,20 @@ RSpec.describe Backup::Files do
|
|||
allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
|
||||
end
|
||||
|
||||
RSpec::Matchers.define :eq_statuslist do |expected|
|
||||
match do |actual|
|
||||
actual.map(&:exitstatus) == expected.map(&:exitstatus)
|
||||
end
|
||||
|
||||
description do
|
||||
'be an Array of Process::Status with equal exitstatus against expected'
|
||||
end
|
||||
|
||||
failure_message do |actual|
|
||||
"expected #{actual} exitstatuses list to be equal #{expected} exitstatuses list"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#restore' do
|
||||
subject { described_class.new('registry', '/var/gitlab-registry') }
|
||||
|
||||
|
@ -35,8 +53,9 @@ RSpec.describe Backup::Files do
|
|||
|
||||
describe 'folders with permission' do
|
||||
before do
|
||||
allow(subject).to receive(:run_pipeline!).and_return(true)
|
||||
allow(subject).to receive(:run_pipeline!).and_return([[true, true], ''])
|
||||
allow(subject).to receive(:backup_existing_files).and_return(true)
|
||||
allow(subject).to receive(:pipeline_succeeded?).and_return(true)
|
||||
allow(Dir).to receive(:glob).with("/var/gitlab-registry/*", File::FNM_DOTMATCH).and_return(["/var/gitlab-registry/.", "/var/gitlab-registry/..", "/var/gitlab-registry/sample1"])
|
||||
end
|
||||
|
||||
|
@ -54,14 +73,22 @@ RSpec.describe Backup::Files do
|
|||
expect(subject).to receive(:tar).and_return('blabla-tar')
|
||||
|
||||
expect(subject).to receive(:run_pipeline!).with([%w(gzip -cd), %w(blabla-tar --unlink-first --recursive-unlink -C /var/gitlab-registry -xf -)], any_args)
|
||||
expect(subject).to receive(:pipeline_succeeded?).and_return(true)
|
||||
subject.restore
|
||||
end
|
||||
|
||||
it 'raises an error on failure' do
|
||||
expect(subject).to receive(:pipeline_succeeded?).and_return(false)
|
||||
|
||||
expect { subject.restore }.to raise_error(/Restore operation failed:/)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'folders without permissions' do
|
||||
before do
|
||||
allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
|
||||
allow(subject).to receive(:run_pipeline!).and_return(true)
|
||||
allow(subject).to receive(:run_pipeline!).and_return([[true, true], ''])
|
||||
allow(subject).to receive(:pipeline_succeeded?).and_return(true)
|
||||
end
|
||||
|
||||
it 'shows error message' do
|
||||
|
@ -73,7 +100,8 @@ RSpec.describe Backup::Files do
|
|||
describe 'folders that are a mountpoint' do
|
||||
before do
|
||||
allow(FileUtils).to receive(:mv).and_raise(Errno::EBUSY)
|
||||
allow(subject).to receive(:run_pipeline!).and_return(true)
|
||||
allow(subject).to receive(:run_pipeline!).and_return([[true, true], ''])
|
||||
allow(subject).to receive(:pipeline_succeeded?).and_return(true)
|
||||
end
|
||||
|
||||
it 'shows error message' do
|
||||
|
@ -89,7 +117,8 @@ RSpec.describe Backup::Files do
|
|||
subject { described_class.new('pages', '/var/gitlab-pages', excludes: ['@pages.tmp']) }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:run_pipeline!).and_return(true)
|
||||
allow(subject).to receive(:run_pipeline!).and_return([[true, true], ''])
|
||||
allow(subject).to receive(:pipeline_succeeded?).and_return(true)
|
||||
end
|
||||
|
||||
it 'raises no errors' do
|
||||
|
@ -103,29 +132,190 @@ RSpec.describe Backup::Files do
|
|||
subject.dump
|
||||
end
|
||||
|
||||
it 'raises an error on failure' do
|
||||
allow(subject).to receive(:run_pipeline!).and_return([[true, true], ''])
|
||||
expect(subject).to receive(:pipeline_succeeded?).and_return(false)
|
||||
|
||||
expect do
|
||||
subject.dump
|
||||
end.to raise_error(/Backup operation failed:/)
|
||||
end
|
||||
|
||||
describe 'with STRATEGY=copy' do
|
||||
before do
|
||||
stub_env('STRATEGY', 'copy')
|
||||
allow(Gitlab.config.backup).to receive(:path) { '/var/gitlab-backup' }
|
||||
allow(File).to receive(:realpath).with("/var/gitlab-backup").and_return("/var/gitlab-backup")
|
||||
end
|
||||
|
||||
it 'excludes tmp dirs from rsync' do
|
||||
allow(Gitlab.config.backup).to receive(:path) { '/var/gitlab-backup' }
|
||||
allow(File).to receive(:realpath).with("/var/gitlab-backup").and_return("/var/gitlab-backup")
|
||||
|
||||
expect(Gitlab::Popen).to receive(:popen).with(%w(rsync -a --exclude=lost+found --exclude=/@pages.tmp /var/gitlab-pages /var/gitlab-backup)).and_return(['', 0])
|
||||
|
||||
subject.dump
|
||||
end
|
||||
end
|
||||
|
||||
describe '#exclude_dirs' do
|
||||
it 'prepends a leading dot slash to tar excludes' do
|
||||
expect(subject.exclude_dirs(:tar)).to eq(['--exclude=lost+found', '--exclude=./@pages.tmp'])
|
||||
end
|
||||
it 'raises an error and outputs an error message if rsync failed' do
|
||||
allow(Gitlab::Popen).to receive(:popen).with(%w(rsync -a --exclude=lost+found --exclude=/@pages.tmp /var/gitlab-pages /var/gitlab-backup)).and_return(['rsync failed', 1])
|
||||
|
||||
it 'prepends a leading slash to rsync excludes' do
|
||||
expect(subject.exclude_dirs(:rsync)).to eq(['--exclude=lost+found', '--exclude=/@pages.tmp'])
|
||||
expect do
|
||||
subject.dump
|
||||
end.to output(/rsync failed/).to_stdout
|
||||
.and raise_error(/Backup failed/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#exclude_dirs' do
|
||||
subject { described_class.new('pages', '/var/gitlab-pages', excludes: ['@pages.tmp']) }
|
||||
|
||||
it 'prepends a leading dot slash to tar excludes' do
|
||||
expect(subject.exclude_dirs(:tar)).to eq(['--exclude=lost+found', '--exclude=./@pages.tmp'])
|
||||
end
|
||||
|
||||
it 'prepends a leading slash to rsync excludes' do
|
||||
expect(subject.exclude_dirs(:rsync)).to eq(['--exclude=lost+found', '--exclude=/@pages.tmp'])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#run_pipeline!' do
|
||||
subject { described_class.new('registry', '/var/gitlab-registry') }
|
||||
|
||||
it 'executes an Open3.pipeline for cmd_list' do
|
||||
expect(Open3).to receive(:pipeline).with(%w[whew command], %w[another cmd], any_args)
|
||||
|
||||
subject.run_pipeline!([%w[whew command], %w[another cmd]])
|
||||
end
|
||||
|
||||
it 'returns an empty output on success pipeline' do
|
||||
expect(subject.run_pipeline!(%w[true true])[1]).to eq('')
|
||||
end
|
||||
|
||||
it 'returns the stderr for failed pipeline' do
|
||||
expect(
|
||||
subject.run_pipeline!(['echo OMG: failed command present 1>&2; false', 'true'])[1]
|
||||
).to match(/OMG: failed/)
|
||||
end
|
||||
|
||||
it 'returns the success status list on success pipeline' do
|
||||
expect(
|
||||
subject.run_pipeline!(%w[true true])[0]
|
||||
).to eq_statuslist([status_0, status_0])
|
||||
end
|
||||
|
||||
it 'returns the failed status in status list for failed commands in pipeline' do
|
||||
expect(subject.run_pipeline!(%w[false true true])[0]).to eq_statuslist([status_1, status_0, status_0])
|
||||
expect(subject.run_pipeline!(%w[true false true])[0]).to eq_statuslist([status_0, status_1, status_0])
|
||||
expect(subject.run_pipeline!(%w[false false true])[0]).to eq_statuslist([status_1, status_1, status_0])
|
||||
expect(subject.run_pipeline!(%w[false true false])[0]).to eq_statuslist([status_1, status_0, status_1])
|
||||
expect(subject.run_pipeline!(%w[false false false])[0]).to eq_statuslist([status_1, status_1, status_1])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#pipeline_succeeded?' do
|
||||
subject { described_class.new('registry', '/var/gitlab-registry') }
|
||||
|
||||
it 'returns true if both tar and gzip succeeeded' do
|
||||
expect(
|
||||
subject.pipeline_succeeded?(tar_status: status_0, gzip_status: status_0, output: 'any_output')
|
||||
).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false if gzip failed' do
|
||||
expect(
|
||||
subject.pipeline_succeeded?(tar_status: status_1, gzip_status: status_1, output: 'any_output')
|
||||
).to be_falsey
|
||||
end
|
||||
|
||||
context 'if gzip succeeded and tar failed non-critically' do
|
||||
before do
|
||||
allow(subject).to receive(:tar_ignore_non_success?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(
|
||||
subject.pipeline_succeeded?(tar_status: status_1, gzip_status: status_0, output: 'any_output')
|
||||
).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'if gzip succeeded and tar failed in other cases' do
|
||||
before do
|
||||
allow(subject).to receive(:tar_ignore_non_success?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(
|
||||
subject.pipeline_succeeded?(tar_status: status_1, gzip_status: status_0, output: 'any_output')
|
||||
).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tar_ignore_non_success?' do
|
||||
subject { described_class.new('registry', '/var/gitlab-registry') }
|
||||
|
||||
context 'if `tar` command exits with 1 exitstatus' do
|
||||
it 'returns true' do
|
||||
expect(
|
||||
subject.tar_ignore_non_success?(1, 'any_output')
|
||||
).to be_truthy
|
||||
end
|
||||
|
||||
it 'outputs a warning' do
|
||||
expect do
|
||||
subject.tar_ignore_non_success?(1, 'any_output')
|
||||
end.to output(/Ignoring tar exit status 1/).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context 'if `tar` command exits with 2 exitstatus with non-critical warning' do
|
||||
before do
|
||||
allow(subject).to receive(:noncritical_warning?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(
|
||||
subject.tar_ignore_non_success?(2, 'any_output')
|
||||
).to be_truthy
|
||||
end
|
||||
|
||||
it 'outputs a warning' do
|
||||
expect do
|
||||
subject.tar_ignore_non_success?(2, 'any_output')
|
||||
end.to output(/Ignoring non-success exit status/).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context 'if `tar` command exits with any other unlisted error' do
|
||||
before do
|
||||
allow(subject).to receive(:noncritical_warning?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(
|
||||
subject.tar_ignore_non_success?(2, 'any_output')
|
||||
).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#noncritical_warning?' do
|
||||
subject { described_class.new('registry', '/var/gitlab-registry') }
|
||||
|
||||
it 'returns true if given text matches noncritical warnings list' do
|
||||
expect(
|
||||
subject.noncritical_warning?('tar: .: Cannot mkdir: No such file or directory')
|
||||
).to be_truthy
|
||||
|
||||
expect(
|
||||
subject.noncritical_warning?('gtar: .: Cannot mkdir: No such file or directory')
|
||||
).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false otherwize' do
|
||||
expect(
|
||||
subject.noncritical_warning?('unknown message')
|
||||
).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,7 +23,8 @@ RSpec.describe Backup::Pages do
|
|||
allow(Gitlab.config.pages).to receive(:path) { '/var/gitlab-pages' }
|
||||
|
||||
expect(subject).to receive(:tar).and_return('blabla-tar')
|
||||
expect(subject).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./@pages.tmp -C /var/gitlab-pages -cf - .), 'gzip -c -1'], any_args)
|
||||
expect(subject).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./@pages.tmp -C /var/gitlab-pages -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
||||
expect(subject).to receive(:pipeline_succeeded?).and_return(true)
|
||||
subject.dump
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,8 @@ RSpec.describe Backup::Uploads do
|
|||
|
||||
it 'excludes tmp from backup tar' do
|
||||
expect(backup).to receive(:tar).and_return('blabla-tar')
|
||||
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/uploads -cf - .), 'gzip -c -1'], any_args)
|
||||
expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/uploads -cf - .), 'gzip -c -1'], any_args).and_return([[true, true], ''])
|
||||
expect(backup).to receive(:pipeline_succeeded?).and_return(true)
|
||||
backup.dump
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12363,10 +12363,10 @@ vue-loader@^15.9.3:
|
|||
vue-hot-reload-api "^2.3.0"
|
||||
vue-style-loader "^4.1.0"
|
||||
|
||||
vue-router@^3.4.7:
|
||||
version "3.4.7"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.7.tgz#bf189bafd16f4e4ef783c4a6250a3090f2c1fa1b"
|
||||
integrity sha512-CbHXue5BLrDivOk5O4eZ0WT4Yj8XwdXa4kCnsEIOzYUPF/07ZukayA2jGxDCJxLc9SgVQX9QX0OuGOwGlVB4Qg==
|
||||
vue-router@3.4.5:
|
||||
version "3.4.5"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.5.tgz#d396ec037b35931bdd1e9b7edd86f9788dc15175"
|
||||
integrity sha512-ioRY5QyDpXM9TDjOX6hX79gtaMXSVDDzSlbIlyAmbHNteIL81WIVB2e+jbzV23vzxtoV0krdS2XHm+GxFg+Nxg==
|
||||
|
||||
vue-runtime-helpers@^1.1.2:
|
||||
version "1.1.2"
|
||||
|
|
Loading…
Reference in a new issue