summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-08-17 12:10:12 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-08-17 12:10:12 +0000
commit4203215d542505bba491a01d637479934c8005d6 (patch)
tree000a956ac60247021ff8c36a1a17a1ea6ed1ff38
parent325318e2ddfcaedf0527053dd3c9bd62db547089 (diff)
downloadgitlab-ce-4203215d542505bba491a01d637479934c8005d6.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js27
-rw-r--r--app/assets/javascripts/clusters/forms/components/integration_form.vue133
-rw-r--r--app/assets/javascripts/clusters/forms/show/index.js8
-rw-r--r--app/assets/javascripts/clusters/forms/stores/state.js5
-rw-r--r--app/assets/javascripts/diffs/components/app.vue53
-rw-r--r--app/assets/javascripts/import_projects/components/import_projects_table.vue86
-rw-r--r--app/assets/javascripts/import_projects/components/imported_project_table_row.vue27
-rw-r--r--app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue6
-rw-r--r--app/assets/javascripts/import_projects/components/provider_repo_table_row.vue89
-rw-r--r--app/assets/javascripts/import_projects/event_hub.js3
-rw-r--r--app/assets/javascripts/import_projects/index.js27
-rw-r--r--app/assets/javascripts/import_projects/store/actions.js89
-rw-r--r--app/assets/javascripts/import_projects/store/getters.js42
-rw-r--r--app/assets/javascripts/import_projects/store/index.js8
-rw-r--r--app/assets/javascripts/import_projects/store/mutation_types.js6
-rw-r--r--app/assets/javascripts/import_projects/store/mutations.js92
-rw-r--r--app/assets/javascripts/import_projects/store/state.js13
-rw-r--r--app/assets/javascripts/import_projects/utils.js7
-rw-r--r--app/assets/javascripts/incidents/components/incidents_list.vue9
-rw-r--r--app/assets/javascripts/incidents/list.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue10
-rw-r--r--app/controllers/projects/issues_controller.rb14
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb2
-rw-r--r--app/finders/concerns/merged_at_filter.rb10
-rw-r--r--app/helpers/clusters_helper.rb11
-rw-r--r--app/helpers/projects/incidents_helper.rb1
-rw-r--r--app/helpers/projects_helper.rb13
-rw-r--r--app/models/ci/pipeline.rb10
-rw-r--r--app/models/clusters/providers/aws.rb3
-rw-r--r--app/models/notification_setting.rb6
-rw-r--r--app/serializers/diffs_metadata_entity.rb19
-rw-r--r--app/services/issues/build_service.rb2
-rw-r--r--app/views/clusters/clusters/_gitlab_integration_form.html.haml26
-rw-r--r--app/views/import/_githubish_status.html.haml1
-rw-r--r--app/views/profiles/chat_names/new.html.haml2
-rw-r--r--app/views/projects/blob/edit.html.haml3
-rw-r--r--app/views/projects/blob/new.html.haml3
-rw-r--r--app/views/projects/merge_requests/_widget.html.haml3
-rw-r--r--app/views/projects/merge_requests/show.html.haml3
-rw-r--r--app/views/projects/pages/show.html.haml2
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml6
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml2
-rw-r--r--app/views/shared/issuable/_form.html.haml3
-rw-r--r--app/views/shared/issuable/form/_branch_chooser.html.haml4
-rw-r--r--changelogs/unreleased/198611-monaco_blobs-on-by-default.yml5
-rw-r--r--changelogs/unreleased/219381-issue-type-param-when-creating-issues.yml5
-rw-r--r--config/feature_flags/development/improved_mr_merged_at_queries.yml7
-rw-r--r--db/fixtures/development/14_pipelines.rb2
-rw-r--r--db/migrate/20200812112204_add_index_to_mr_metrics_target_project_id.rb18
-rw-r--r--db/schema_migrations/202008121122041
-rw-r--r--db/structure.sql2
-rw-r--r--doc/.vale/gitlab/spelling-exceptions.txt1
-rw-r--r--doc/administration/gitaly/praefect.md120
-rw-r--r--doc/user/application_security/secret_detection/index.md1
-rw-r--r--doc/user/project/merge_requests/squash_and_merge.md2
-rw-r--r--lib/api/helpers/packages/basic_auth_helpers.rb4
-rw-r--r--lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml146
-rw-r--r--lib/gitlab/config_checker/external_database_checker.rb32
-rw-r--r--lib/gitlab/gon_helper.rb2
-rw-r--r--lib/gitlab/i18n/html_todo.yml852
-rw-r--r--lib/gitlab/markdown_cache.rb2
-rw-r--r--locale/gitlab.pot63
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb3
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb43
-rw-r--r--spec/features/clusters/cluster_detail_page_spec.rb11
-rw-r--r--spec/features/merge_request/user_edits_merge_request_spec.rb23
-rw-r--r--spec/features/projects/badges/coverage_spec.rb2
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js52
-rw-r--r--spec/frontend/clusters/forms/components/integration_form_spec.js120
-rw-r--r--spec/frontend/import_projects/components/import_projects_table_spec.js71
-rw-r--r--spec/frontend/import_projects/components/imported_project_table_row_spec.js65
-rw-r--r--spec/frontend/import_projects/components/provider_repo_table_row_spec.js120
-rw-r--r--spec/frontend/import_projects/store/actions_spec.js171
-rw-r--r--spec/frontend/import_projects/store/getters_spec.js140
-rw-r--r--spec/frontend/import_projects/store/mutations_spec.js278
-rw-r--r--spec/frontend/import_projects/utils_spec.js32
-rw-r--r--spec/frontend/incidents/components/incidents_list_spec.js11
-rw-r--r--spec/frontend/vue_shared/components/time_ago_tooltip_spec.js22
-rw-r--r--spec/helpers/projects/incidents_helper_spec.rb1
-rw-r--r--spec/lib/gitlab/badge/coverage/report_spec.rb2
-rw-r--r--spec/models/ci/pipeline_spec.rb52
-rw-r--r--spec/models/notification_setting_spec.rb40
-rw-r--r--spec/serializers/diffs_metadata_entity_spec.rb1
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb2
-rw-r--r--spec/services/issues/build_service_spec.rb14
85 files changed, 1818 insertions, 1614 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index d0a4073cc00..2324cc5dafb 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -8,14 +8,7 @@ import Flash from '../flash';
import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub';
-import {
- APPLICATION_STATUS,
- INGRESS,
- INGRESS_DOMAIN_SUFFIX,
- CROSSPLANE,
- KNATIVE,
- FLUENTD,
-} from './constants';
+import { APPLICATION_STATUS, CROSSPLANE, KNATIVE, FLUENTD } from './constants';
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue';
@@ -120,10 +113,6 @@ export default class Clusters {
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.tokenField = document.querySelector('.js-cluster-token');
- this.ingressDomainHelpText = document.querySelector('.js-ingress-domain-help-text');
- this.ingressDomainSnippet =
- this.ingressDomainHelpText &&
- this.ingressDomainHelpText.querySelector('.js-ingress-domain-snippet');
initProjectSelectDropdown();
Clusters.initDismissableCallout();
@@ -327,13 +316,6 @@ export default class Clusters {
this.checkForNewInstalls(prevApplicationMap, this.store.state.applications);
this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
- if (this.ingressDomainHelpText) {
- this.toggleIngressDomainHelpText(
- prevApplicationMap[INGRESS],
- this.store.state.applications[INGRESS],
- );
- }
-
if (this.store.state.applications[KNATIVE]?.status === APPLICATION_STATUS.INSTALLED) {
initServerlessSurveyBanner();
}
@@ -505,13 +487,6 @@ export default class Clusters {
});
}
- toggleIngressDomainHelpText({ externalIp }, { externalIp: newExternalIp }) {
- if (externalIp !== newExternalIp) {
- this.ingressDomainHelpText.classList.toggle('hide', !newExternalIp);
- this.ingressDomainSnippet.textContent = `${newExternalIp}${INGRESS_DOMAIN_SUFFIX}`;
- }
- }
-
saveKnativeDomain(data) {
const appId = data.id;
this.store.updateApplication(appId);
diff --git a/app/assets/javascripts/clusters/forms/components/integration_form.vue b/app/assets/javascripts/clusters/forms/components/integration_form.vue
index 1ac9a4ee59e..53e004b4fc0 100644
--- a/app/assets/javascripts/clusters/forms/components/integration_form.vue
+++ b/app/assets/javascripts/clusters/forms/components/integration_form.vue
@@ -1,50 +1,84 @@
<script>
-import { GlFormGroup, GlToggle, GlTooltipDirective } from '@gitlab/ui';
+import {
+ GlFormGroup,
+ GlFormInput,
+ GlToggle,
+ GlTooltipDirective,
+ GlSprintf,
+ GlLink,
+ GlButton,
+} from '@gitlab/ui';
import { mapState } from 'vuex';
export default {
components: {
GlFormGroup,
GlToggle,
+ GlFormInput,
+ GlSprintf,
+ GlLink,
+ GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
+ inject: {
+ autoDevopsHelpPath: {
+ type: String,
+ },
+ externalEndpointHelpPath: {
+ type: String,
+ },
+ },
data() {
return {
toggleEnabled: true,
+ envScope: '*',
+ baseDomainField: '',
+ externalIp: '',
};
},
computed: {
- ...mapState(['enabled', 'editable']),
+ ...mapState([
+ 'enabled',
+ 'editable',
+ 'environmentScope',
+ 'baseDomain',
+ 'applicationIngressExternalIp',
+ ]),
+ canSubmit() {
+ return (
+ this.enabled !== this.toggleEnabled ||
+ this.environmentScope !== this.envScope ||
+ this.baseDomain !== this.baseDomainField
+ );
+ },
},
mounted() {
this.toggleEnabled = this.enabled;
+ this.envScope = this.environmentScope;
+ this.baseDomainField = this.baseDomain;
+ this.externalIp = this.applicationIngressExternalIp;
},
};
</script>
<template>
- <div class="d-flex align-items-center">
+ <div class="d-flex gl-flex-direction-column">
<gl-form-group>
<div class="gl-display-flex gl-align-items-center">
- <h4 class="gl-pr-3 gl-m-0 ">{{ s__('ClusterIntegration|GitLab Integration') }}</h4>
- <input
- id="cluster_enabled"
- class="js-project-feature-toggle-input"
- type="hidden"
- :value="toggleEnabled"
- name="cluster[enabled]"
- />
- <div id="tooltipcontainer" class="js-cluster-enable-toggle-area">
+ <h4 class="gl-pr-3 gl-m-0">{{ s__('ClusterIntegration|GitLab Integration') }}</h4>
+
+ <div class="js-cluster-enable-toggle-area">
<gl-toggle
+ id="toggleCluster"
v-model="toggleEnabled"
v-gl-tooltip:tooltipcontainer
+ name="cluster[enabled]"
class="gl-mb-0 js-project-feature-toggle"
data-qa-selector="integration_status_toggle"
- :aria-describedby="__('Toggle Kubernetes cluster')"
+ aria-describedby="toggleCluster"
:disabled="!editable"
- :is_checked="toggleEnabled"
:title="
s__(
'ClusterIntegration|Enable or disable GitLab\'s connection to your Kubernetes cluster.',
@@ -54,5 +88,76 @@ export default {
</div>
</div>
</gl-form-group>
+
+ <gl-form-group
+ :label="s__('ClusterIntegration|Environment scope')"
+ label-size="sm"
+ label-for="cluster_environment_scope"
+ :description="
+ s__('ClusterIntegration|Choose which of your environments will use this cluster.')
+ "
+ >
+ <gl-form-input
+ id="cluster_environment_scope"
+ v-model="envScope"
+ name="cluster[environment_scope]"
+ class="col-md-6"
+ type="text"
+ />
+ </gl-form-group>
+
+ <gl-form-group
+ :label="s__('ClusterIntegration|Base domain')"
+ label-size="sm"
+ label-for="cluster_base_domain"
+ >
+ <gl-form-input
+ id="cluster_base_domain"
+ v-model="baseDomainField"
+ name="cluster[base_domain]"
+ data-qa-selector="base_domain_field"
+ class="col-md-6"
+ type="text"
+ />
+ <div class="form-text text-muted inline">
+ <gl-sprintf
+ :message="
+ s__(
+ 'ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{linkStart}Auto DevOps.%{linkEnd} The domain should have a wildcard DNS configured matching the domain. ',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="autoDevopsHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ <div v-if="applicationIngressExternalIp" class="js-ingress-domain-help-text inline">
+ {{ s__('ClusterIntegration|Alternatively, ') }}
+ <gl-sprintf :message="s__('ClusterIntegration|%{externalIp}.nip.io')">
+ <template #externalIp>{{ externalIp }}</template>
+ </gl-sprintf>
+ {{ s__('ClusterIntegration|can be used instead of a custom domain. ') }}
+ </div>
+ <gl-sprintf
+ class="inline"
+ :message="s__('ClusterIntegration|%{linkStart}More information%{linkEnd}')"
+ >
+ <template #link="{ content }">
+ <gl-link :href="externalEndpointHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ </gl-form-group>
+ <div v-if="editable" class="form group gl-display-flex gl-justify-content-end">
+ <gl-button
+ category="primary"
+ variant="success"
+ type="submit"
+ :disabled="!canSubmit"
+ :aria-disabled="!canSubmit"
+ data-qa-selector="save_changes_button"
+ >{{ s__('ClusterIntegration|Save changes') }}</gl-button
+ >
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/clusters/forms/show/index.js b/app/assets/javascripts/clusters/forms/show/index.js
index 26a6ab5f0e3..47a3016c777 100644
--- a/app/assets/javascripts/clusters/forms/show/index.js
+++ b/app/assets/javascripts/clusters/forms/show/index.js
@@ -9,13 +9,19 @@ export default () => {
return;
}
+ const { autoDevopsHelpPath, externalEndpointHelpPath } = entryPoint.dataset;
+
// eslint-disable-next-line no-new
new Vue({
el: entryPoint,
store: createStore(entryPoint.dataset),
+ provide: {
+ autoDevopsHelpPath,
+ externalEndpointHelpPath,
+ },
render(createElement) {
- return createElement(IntegrationForm);
+ return createElement(IntegrationForm, {});
},
});
};
diff --git a/app/assets/javascripts/clusters/forms/stores/state.js b/app/assets/javascripts/clusters/forms/stores/state.js
index a88954d5b94..2a96590b5e7 100644
--- a/app/assets/javascripts/clusters/forms/stores/state.js
+++ b/app/assets/javascripts/clusters/forms/stores/state.js
@@ -4,5 +4,10 @@ export default (initialState = {}) => {
return {
enabled: parseBoolean(initialState.enabled),
editable: parseBoolean(initialState.editable),
+ environmentScope: initialState.environmentScope,
+ baseDomain: initialState.baseDomain,
+ applicationIngressExternalIp: initialState.applicationIngressExternalIp,
+ autoDevopsHelpPath: initialState.autoDevopsHelpPath,
+ externalEndpointHelpPath: initialState.externalEndpointHelpPath,
};
};
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index ad4e09a00fa..a6c2e84310b 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -1,9 +1,10 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
-import { GlLoadingIcon, GlButtonGroup, GlButton } from '@gitlab/ui';
+import { GlLoadingIcon, GlButtonGroup, GlButton, GlAlert } from '@gitlab/ui';
import Mousetrap from 'mousetrap';
import { __ } from '~/locale';
import createFlash from '~/flash';
+import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { isSingleViewStyle } from '~/helpers/diffs_helper';
@@ -38,6 +39,7 @@ export default {
PanelResizer,
GlButtonGroup,
GlButton,
+ GlAlert,
},
mixins: [glFeatureFlagsMixin()],
props: {
@@ -133,6 +135,9 @@ export default {
'startVersion',
'currentDiffFileId',
'isTreeLoaded',
+ 'conflictResolutionPath',
+ 'canMerge',
+ 'hasConflicts',
]),
...mapGetters('diffs', ['isParallelView', 'currentDiffIndex']),
...mapGetters(['isNotesFetched', 'getNoteableData']),
@@ -161,6 +166,9 @@ export default {
isLimitedContainer() {
return !this.showTreeList && !this.isParallelView && !this.isFluidLayout;
},
+ isDiffHead() {
+ return parseBoolean(getParameterByName('diff_head'));
+ },
},
watch: {
commit(newCommit, oldCommit) {
@@ -423,6 +431,49 @@ export default {
/>
<div
+ v-if="isDiffHead && hasConflicts"
+ :class="{
+ [CENTERED_LIMITED_CONTAINER_CLASSES]: isLimitedContainer,
+ }"
+ >
+ <gl-alert
+ :dismissible="false"
+ :title="__('There are merge conflicts')"
+ variant="warning"
+ class="w-100 mb-3"
+ >
+ <p class="mb-1">
+ {{ __('The comparison view may be inaccurate due to merge conflicts.') }}
+ </p>
+ <p class="mb-0">
+ {{
+ __(
+ 'Resolve these conflicts or ask someone with write access to this repository to merge it locally.',
+ )
+ }}
+ </p>
+ <template #actions>
+ <gl-button
+ v-if="conflictResolutionPath"
+ :href="conflictResolutionPath"
+ variant="info"
+ class="mr-3 gl-alert-action"
+ >
+ {{ __('Resolve conflicts') }}
+ </gl-button>
+ <gl-button
+ v-if="canMerge"
+ class="gl-alert-action"
+ data-toggle="modal"
+ data-target="#modal_merge_info"
+ >
+ {{ __('Merge locally') }}
+ </gl-button>
+ </template>
+ </gl-alert>
+ </div>
+
+ <div
:data-can-create-note="getNoteableData.current_user.can_create_note"
class="files d-flex"
>
diff --git a/app/assets/javascripts/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_projects/components/import_projects_table.vue
index 6a467fb8c6a..3ae12363f39 100644
--- a/app/assets/javascripts/import_projects/components/import_projects_table.vue
+++ b/app/assets/javascripts/import_projects/components/import_projects_table.vue
@@ -6,7 +6,7 @@ import { __, sprintf } from '~/locale';
import ImportedProjectTableRow from './imported_project_table_row.vue';
import ProviderRepoTableRow from './provider_repo_table_row.vue';
import IncompatibleRepoTableRow from './incompatible_repo_table_row.vue';
-import eventHub from '../event_hub';
+import { isProjectImportable } from '../utils';
const reposFetchThrottleDelay = 1000;
@@ -32,20 +32,29 @@ export default {
},
computed: {
- ...mapState([
- 'importedProjects',
- 'providerRepos',
- 'incompatibleRepos',
- 'isLoadingRepos',
- 'filter',
- ]),
+ ...mapState(['filter', 'repositories', 'namespaces', 'defaultTargetNamespace']),
...mapGetters([
+ 'isLoading',
'isImportingAnyRepo',
- 'hasProviderRepos',
- 'hasImportedProjects',
+ 'hasImportableRepos',
'hasIncompatibleRepos',
]),
+ availableNamespaces() {
+ const serializedNamespaces = this.namespaces.map(({ fullPath }) => ({
+ id: fullPath,
+ text: fullPath,
+ }));
+
+ return [
+ { text: __('Groups'), children: serializedNamespaces },
+ {
+ text: __('Users'),
+ children: [{ id: this.defaultTargetNamespace, text: this.defaultTargetNamespace }],
+ },
+ ];
+ },
+
importAllButtonText() {
return this.hasIncompatibleRepos
? __('Import all compatible repositories')
@@ -64,7 +73,8 @@ export default {
},
mounted() {
- return this.fetchRepos();
+ this.fetchNamespaces();
+ this.fetchRepos();
},
beforeDestroy() {
@@ -75,17 +85,13 @@ export default {
methods: {
...mapActions([
'fetchRepos',
- 'fetchReposFiltered',
- 'fetchJobs',
+ 'fetchNamespaces',
'stopJobsPolling',
'clearJobsEtagPoll',
'setFilter',
+ 'importAll',
]),
- importAll() {
- eventHub.$emit('importAll');
- },
-
handleFilterInput({ target }) {
this.setFilter(target.value);
},
@@ -93,6 +99,8 @@ export default {
throttledFetchRepos: throttle(function fetch() {
this.fetchRepos();
}, reposFetchThrottleDelay),
+
+ isProjectImportable,
},
};
</script>
@@ -103,21 +111,17 @@ export default {
{{ s__('ImportProjects|Select the projects you want to import') }}
</p>
<template v-if="hasIncompatibleRepos">
- <slot name="incompatible-repos-warning"> </slot>
+ <slot name="incompatible-repos-warning"></slot>
</template>
- <div
- v-if="!isLoadingRepos"
- class="d-flex justify-content-between align-items-end flex-wrap mb-3"
- >
+ <div v-if="!isLoading" class="d-flex justify-content-between align-items-end flex-wrap mb-3">
<gl-button
variant="success"
:loading="isImportingAnyRepo"
- :disabled="!hasProviderRepos"
+ :disabled="!hasImportableRepos"
type="button"
@click="importAll"
+ >{{ importAllButtonText }}</gl-button
>
- {{ importAllButtonText }}
- </gl-button>
<slot name="actions"></slot>
<form v-if="filterable" class="gl-ml-auto" novalidate @submit.prevent>
<input
@@ -134,14 +138,11 @@ export default {
</form>
</div>
<gl-loading-icon
- v-if="isLoadingRepos"
+ v-if="isLoading"
class="js-loading-button-icon import-projects-loading-icon"
size="md"
/>
- <div
- v-else-if="hasProviderRepos || hasImportedProjects || hasIncompatibleRepos"
- class="table-responsive"
- >
+ <div v-else-if="repositories.length" class="table-responsive">
<table class="table import-table">
<thead>
<th class="import-jobs-from-col">{{ fromHeaderText }}</th>
@@ -150,17 +151,20 @@ export default {
<th class="import-jobs-cta-col"></th>
</thead>
<tbody>
- <imported-project-table-row
- v-for="project in importedProjects"
- :key="project.id"
- :project="project"
- />
- <provider-repo-table-row v-for="repo in providerRepos" :key="repo.id" :repo="repo" />
- <incompatible-repo-table-row
- v-for="repo in incompatibleRepos"
- :key="repo.id"
- :repo="repo"
- />
+ <template v-for="repo in repositories">
+ <incompatible-repo-table-row
+ v-if="repo.importSource.incompatible"
+ :key="repo.importSource.id"
+ :repo="repo"
+ />
+ <provider-repo-table-row
+ v-else-if="isProjectImportable(repo)"
+ :key="repo.importSource.id"
+ :repo="repo"
+ :available-namespaces="availableNamespaces"
+ />
+ <imported-project-table-row v-else :key="repo.importSource.id" :project="repo" />
+ </template>
</tbody>
</table>
</div>
diff --git a/app/assets/javascripts/import_projects/components/imported_project_table_row.vue b/app/assets/javascripts/import_projects/components/imported_project_table_row.vue
index a1c54b11276..50e735b4478 100644
--- a/app/assets/javascripts/import_projects/components/imported_project_table_row.vue
+++ b/app/assets/javascripts/import_projects/components/imported_project_table_row.vue
@@ -18,7 +18,7 @@ export default {
computed: {
displayFullPath() {
- return this.project.fullPath.replace(/^\//, '');
+ return this.project.importedProject.fullPath.replace(/^\//, '');
},
isFinished() {
@@ -29,29 +29,30 @@ export default {
</script>
<template>
- <tr class="js-imported-project import-row">
+ <tr class="import-row">
<td>
<a
- :href="project.providerLink"
+ :href="project.importSource.providerLink"
rel="noreferrer noopener"
target="_blank"
- class="js-provider-link"
- >
- {{ project.importSource }}
- <gl-icon v-if="project.providerLink" name="external-link" />
+ data-testid="providerLink"
+ >{{ project.importSource.fullName }}
+ <gl-icon v-if="project.importSource.providerLink" name="external-link" />
</a>
</td>
- <td class="js-full-path">{{ displayFullPath }}</td>
- <td><import-status :status="project.importStatus" /></td>
+ <td data-testid="fullPath">{{ displayFullPath }}</td>
+ <td>
+ <import-status :status="project.importStatus" />
+ </td>
<td>
<a
v-if="isFinished"
- class="btn btn-default js-go-to-project"
- :href="project.fullPath"
+ class="btn btn-default"
+ data-testid="goToProject"
+ :href="project.importedProject.fullPath"
rel="noreferrer noopener"
target="_blank"
- >
- {{ __('Go to project') }}
+ >{{ __('Go to project') }}
</a>
</td>
</tr>
diff --git a/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue b/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue
index d44c155a84e..3140585ccd7 100644
--- a/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue
+++ b/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue
@@ -18,9 +18,9 @@ export default {
<template>
<tr class="import-row">
<td>
- <a :href="repo.providerLink" rel="noreferrer noopener" target="_blank">
- {{ repo.fullName }}
- <gl-icon v-if="repo.providerLink" name="external-link" />
+ <a :href="repo.importSource.providerLink" rel="noreferrer noopener" target="_blank"
+ >{{ repo.importSource.fullName }}
+ <gl-icon v-if="repo.importSource.providerLink" name="external-link" />
</a>
</td>
<td></td>
diff --git a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
index a03e3d50135..5d73ac44bab 100644
--- a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
+++ b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
@@ -3,8 +3,6 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import { GlIcon } from '@gitlab/ui';
import Select2Select from '~/vue_shared/components/select2_select.vue';
import { __ } from '~/locale';
-import eventHub from '../event_hub';
-import { STATUSES } from '../constants';
import ImportStatus from './import_status.vue';
export default {
@@ -19,19 +17,19 @@ export default {
type: Object,
required: true,
},
- },
-
- data() {
- return {
- targetNamespace: this.$store.state.defaultTargetNamespace,
- newName: this.repo.sanitizedName,
- };
+ availableNamespaces: {
+ type: Array,
+ required: true,
+ },
},
computed: {
- ...mapState(['namespaces', 'reposBeingImported', 'ciCdOnly']),
+ ...mapState(['ciCdOnly']),
+ ...mapGetters(['getImportTarget']),
- ...mapGetters(['namespaceSelectOptions']),
+ importTarget() {
+ return this.getImportTarget(this.repo.importSource.id);
+ },
importButtonText() {
return this.ciCdOnly ? __('Connect') : __('Import');
@@ -39,37 +37,36 @@ export default {
select2Options() {
return {
- data: this.namespaceSelectOptions,
- containerCssClass:
- 'import-namespace-select js-namespace-select qa-project-namespace-select w-auto',
+ data: this.availableNamespaces,
+ containerCssClass: 'import-namespace-select qa-project-namespace-select w-auto',
};
},
- isLoadingImport() {
- return this.reposBeingImported.includes(this.repo.id);
+ targetNamespaceSelect: {
+ get() {
+ return this.importTarget.targetNamespace;
+ },
+ set(value) {
+ this.updateImportTarget({ targetNamespace: value });
+ },
},
- status() {
- return this.isLoadingImport ? STATUSES.SCHEDULING : STATUSES.NONE;
+ newNameInput: {
+ get() {
+ return this.importTarget.newName;
+ },
+ set(value) {
+ this.updateImportTarget({ newName: value });
+ },
},
},
- created() {
- eventHub.$on('importAll', this.importRepo);
- },
-
- beforeDestroy() {
- eventHub.$off('importAll', this.importRepo);
- },
-
methods: {
- ...mapActions(['fetchImport']),
-
- importRepo() {
- return this.fetchImport({
- newName: this.newName,
- targetNamespace: this.targetNamespace,
- repo: this.repo,
+ ...mapActions(['fetchImport', 'setImportTarget']),
+ updateImportTarget(changedValues) {
+ this.setImportTarget({
+ repoId: this.repo.importSource.id,
+ importTarget: { ...this.importTarget, ...changedValues },
});
},
},
@@ -77,36 +74,36 @@ export default {
</script>
<template>
- <tr class="qa-project-import-row js-provider-repo import-row">
+ <tr class="qa-project-import-row import-row">
<td>
<a
- :href="repo.providerLink"
+ :href="repo.importSource.providerLink"
rel="noreferrer noopener"
target="_blank"
- class="js-provider-link"
- >
- {{ repo.fullName }}
- <gl-icon v-if="repo.providerLink" name="external-link" />
+ data-testid="providerLink"
+ >{{ repo.importSource.fullName }}
+ <gl-icon v-if="repo.importSource.providerLink" name="external-link" />
</a>
</td>
<td class="d-flex flex-wrap flex-lg-nowrap">
- <select2-select v-model="targetNamespace" :options="select2Options" />
+ <select2-select v-model="targetNamespaceSelect" :options="select2Options" />
<span class="px-2 import-slash-divider d-flex justify-content-center align-items-center"
>/</span
>
<input
- v-model="newName"
+ v-model="newNameInput"
type="text"
- class="form-control import-project-name-input js-new-name qa-project-path-field"
+ class="form-control import-project-name-input qa-project-path-field"
/>
</td>
- <td><import-status :status="status" /></td>
+ <td>
+ <import-status :status="repo.importStatus" />
+ </td>
<td>
<button
- v-if="!isLoadingImport"
type="button"
- class="qa-import-button js-import-button btn btn-default"
- @click="importRepo"
+ class="qa-import-button btn btn-default"
+ @click="fetchImport(repo.importSource.id)"
>
{{ importButtonText }}
</button>
diff --git a/app/assets/javascripts/import_projects/event_hub.js b/app/assets/javascripts/import_projects/event_hub.js
deleted file mode 100644
index e31806ad199..00000000000
--- a/app/assets/javascripts/import_projects/event_hub.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import createEventHub from '~/helpers/event_hub_factory';
-
-export default createEventHub();
diff --git a/app/assets/javascripts/import_projects/index.js b/app/assets/javascripts/import_projects/index.js
index 68ba04aa9dd..e616acdb310 100644
--- a/app/assets/javascripts/import_projects/index.js
+++ b/app/assets/javascripts/import_projects/index.js
@@ -8,22 +8,29 @@ Vue.use(Translate);
export function initStoreFromElement(element) {
const {
- reposPath,
- provider,
+ ciCdOnly,
canSelectNamespace,
+ provider,
+
+ reposPath,
jobsPath,
importPath,
- ciCdOnly,
+ namespacesPath,
} = element.dataset;
return createStore({
- reposPath,
- provider,
- jobsPath,
- importPath,
- defaultTargetNamespace: gon.current_username,
- ciCdOnly: parseBoolean(ciCdOnly),
- canSelectNamespace: parseBoolean(canSelectNamespace),
+ initialState: {
+ defaultTargetNamespace: gon.current_username,
+ ciCdOnly: parseBoolean(ciCdOnly),
+ canSelectNamespace: parseBoolean(canSelectNamespace),
+ provider,
+ },
+ endpoints: {
+ reposPath,
+ jobsPath,
+ importPath,
+ namespacesPath,
+ },
});
}
diff --git a/app/assets/javascripts/import_projects/store/actions.js b/app/assets/javascripts/import_projects/store/actions.js
index fedae173af7..5765dea51ac 100644
--- a/app/assets/javascripts/import_projects/store/actions.js
+++ b/app/assets/javascripts/import_projects/store/actions.js
@@ -1,38 +1,57 @@
import Visibility from 'visibilityjs';
import * as types from './mutation_types';
+import { isProjectImportable } from '../utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import Poll from '~/lib/utils/poll';
import { visitUrl } from '~/lib/utils/url_utility';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import axios from '~/lib/utils/axios_utils';
-import { jobsPathWithFilter, reposPathWithFilter } from './getters';
let eTagPoll;
const hasRedirectInError = e => e?.response?.data?.error?.redirect;
const redirectToUrlInError = e => visitUrl(e.response.data.error.redirect);
+const pathWithFilter = ({ path, filter = '' }) => (filter ? `${path}?filter=${filter}` : path);
-export const clearJobsEtagPoll = () => {
+const isRequired = () => {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ throw new Error('param is required');
+};
+
+const clearJobsEtagPoll = () => {
eTagPoll = null;
};
-export const stopJobsPolling = () => {
+
+const stopJobsPolling = () => {
if (eTagPoll) eTagPoll.stop();
};
-export const restartJobsPolling = () => {
+
+const restartJobsPolling = () => {
if (eTagPoll) eTagPoll.restart();
};
-export const setFilter = ({ commit }, filter) => commit(types.SET_FILTER, filter);
+const setFilter = ({ commit }, filter) => commit(types.SET_FILTER, filter);
+
+const setImportTarget = ({ commit }, { repoId, importTarget }) =>
+ commit(types.SET_IMPORT_TARGET, { repoId, importTarget });
-export const fetchRepos = ({ state, dispatch, commit }) => {
+const importAll = ({ state, dispatch }) => {
+ return Promise.all(
+ state.repositories
+ .filter(isProjectImportable)
+ .map(r => dispatch('fetchImport', r.importSource.id)),
+ );
+};
+
+const fetchReposFactory = (reposPath = isRequired()) => ({ state, dispatch, commit }) => {
dispatch('stopJobsPolling');
commit(types.REQUEST_REPOS);
- const { provider } = state;
+ const { provider, filter } = state;
return axios
- .get(reposPathWithFilter(state))
+ .get(pathWithFilter({ path: reposPath, filter }))
.then(({ data }) =>
commit(types.RECEIVE_REPOS_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true })),
)
@@ -52,22 +71,24 @@ export const fetchRepos = ({ state, dispatch, commit }) => {
});
};
-export const fetchImport = ({ state, commit }, { newName, targetNamespace, repo }) => {
- if (!state.reposBeingImported.includes(repo.id)) {
- commit(types.REQUEST_IMPORT, repo.id);
- }
+const fetchImportFactory = (importPath = isRequired()) => ({ state, commit, getters }, repoId) => {
+ const { ciCdOnly } = state;
+ const importTarget = getters.getImportTarget(repoId);
+
+ commit(types.REQUEST_IMPORT, { repoId, importTarget });
+ const { newName, targetNamespace } = importTarget;
return axios
- .post(state.importPath, {
- ci_cd_only: state.ciCdOnly,
+ .post(importPath, {
+ repo_id: repoId,
+ ci_cd_only: ciCdOnly,
new_name: newName,
- repo_id: repo.id,
target_namespace: targetNamespace,
})
.then(({ data }) =>
commit(types.RECEIVE_IMPORT_SUCCESS, {
importedProject: convertObjectPropsToCamelCase(data, { deep: true }),
- repoId: repo.id,
+ repoId,
}),
)
.catch(e => {
@@ -84,14 +105,11 @@ export const fetchImport = ({ state, commit }, { newName, targetNamespace, repo
createFlash(flashMessage);
- commit(types.RECEIVE_IMPORT_ERROR, repo.id);
+ commit(types.RECEIVE_IMPORT_ERROR, repoId);
});
};
-export const receiveJobsSuccess = ({ commit }, updatedProjects) =>
- commit(types.RECEIVE_JOBS_SUCCESS, updatedProjects);
-
-export const fetchJobs = ({ state, commit, dispatch }) => {
+export const fetchJobsFactory = (jobsPath = isRequired()) => ({ state, commit, dispatch }) => {
const { filter } = state;
if (eTagPoll) {
@@ -101,7 +119,7 @@ export const fetchJobs = ({ state, commit, dispatch }) => {
eTagPoll = new Poll({
resource: {
- fetchJobs: () => axios.get(jobsPathWithFilter(state)),
+ fetchJobs: () => axios.get(pathWithFilter({ path: jobsPath, filter })),
},
method: 'fetchJobs',
successCallback: ({ data }) =>
@@ -128,3 +146,30 @@ export const fetchJobs = ({ state, commit, dispatch }) => {
}
});
};
+
+const fetchNamespacesFactory = (namespacesPath = isRequired()) => ({ commit }) => {
+ commit(types.REQUEST_NAMESPACES);
+ axios
+ .get(namespacesPath)
+ .then(({ data }) =>
+ commit(types.RECEIVE_NAMESPACES_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true })),
+ )
+ .catch(() => {
+ createFlash(s__('ImportProjects|Requesting namespaces failed'));
+
+ commit(types.RECEIVE_NAMESPACES_ERROR);
+ });
+};
+
+export default ({ endpoints = isRequired() }) => ({
+ clearJobsEtagPoll,
+ stopJobsPolling,
+ restartJobsPolling,
+ setFilter,
+ setImportTarget,
+ importAll,
+ fetchRepos: fetchReposFactory(endpoints.reposPath),
+ fetchImport: fetchImportFactory(endpoints.importPath),
+ fetchJobs: fetchJobsFactory(endpoints.jobsPath),
+ fetchNamespaces: fetchNamespacesFactory(endpoints.namespacesPath),
+});
diff --git a/app/assets/javascripts/import_projects/store/getters.js b/app/assets/javascripts/import_projects/store/getters.js
index e6eb8f523de..7d529c94d7d 100644
--- a/app/assets/javascripts/import_projects/store/getters.js
+++ b/app/assets/javascripts/import_projects/store/getters.js
@@ -1,29 +1,27 @@
-import { __ } from '~/locale';
+import { STATUSES } from '../constants';
-export const namespaceSelectOptions = state => {
- const serializedNamespaces = state.namespaces.map(({ fullPath }) => ({
- id: fullPath,
- text: fullPath,
- }));
+export const isLoading = state => state.isLoadingRepos || state.isLoadingNamespaces;
- return [
- { text: __('Groups'), children: serializedNamespaces },
- {
- text: __('Users'),
- children: [{ id: state.defaultTargetNamespace, text: state.defaultTargetNamespace }],
- },
- ];
-};
+export const isImportingAnyRepo = state =>
+ state.repositories.some(repo =>
+ [STATUSES.SCHEDULING, STATUSES.SCHEDULED, STATUSES.STARTED].includes(repo.importStatus),
+ );
-export const isImportingAnyRepo = state => state.reposBeingImported.length > 0;
+export const hasIncompatibleRepos = state =>
+ state.repositories.some(repo => repo.importSource.incompatible);
-export const hasProviderRepos = state => state.providerRepos.length > 0;
+export const hasImportableRepos = state =>
+ state.repositories.some(repo => repo.importStatus === STATUSES.NONE);
-export const hasImportedProjects = state => state.importedProjects.length > 0;
+export const getImportTarget = state => repoId => {
+ if (state.customImportTargets[repoId]) {
+ return state.customImportTargets[repoId];
+ }
-export const hasIncompatibleRepos = state => state.incompatibleRepos.length > 0;
+ const repo = state.repositories.find(r => r.importSource.id === repoId);
-export const reposPathWithFilter = ({ reposPath, filter = '' }) =>
- filter ? `${reposPath}?filter=${filter}` : reposPath;
-export const jobsPathWithFilter = ({ jobsPath, filter = '' }) =>
- filter ? `${jobsPath}?filter=${filter}` : jobsPath;
+ return {
+ newName: repo.importSource.sanitizedName,
+ targetNamespace: state.defaultTargetNamespace,
+ };
+};
diff --git a/app/assets/javascripts/import_projects/store/index.js b/app/assets/javascripts/import_projects/store/index.js
index 29deb7868ba..1419de877fb 100644
--- a/app/assets/javascripts/import_projects/store/index.js
+++ b/app/assets/javascripts/import_projects/store/index.js
@@ -1,18 +1,16 @@
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
-import * as actions from './actions';
+import actionsFactory from './actions';
import * as getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
-export { state, actions, getters, mutations };
-
-export default initialState =>
+export default ({ initialState, endpoints }) =>
new Vuex.Store({
state: { ...state(), ...initialState },
- actions,
+ actions: actionsFactory({ endpoints }),
mutations,
getters,
});
diff --git a/app/assets/javascripts/import_projects/store/mutation_types.js b/app/assets/javascripts/import_projects/store/mutation_types.js
index a23b7eef986..1bcf27d7ba4 100644
--- a/app/assets/javascripts/import_projects/store/mutation_types.js
+++ b/app/assets/javascripts/import_projects/store/mutation_types.js
@@ -2,6 +2,10 @@ export const REQUEST_REPOS = 'REQUEST_REPOS';
export const RECEIVE_REPOS_SUCCESS = 'RECEIVE_REPOS_SUCCESS';
export const RECEIVE_REPOS_ERROR = 'RECEIVE_REPOS_ERROR';
+export const REQUEST_NAMESPACES = 'REQUEST_NAMESPACES';
+export const RECEIVE_NAMESPACES_SUCCESS = 'RECEIVE_NAMESPACES_SUCCESS';
+export const RECEIVE_NAMESPACES_ERROR = 'RECEIVE_NAMESPACES_ERROR';
+
export const REQUEST_IMPORT = 'REQUEST_IMPORT';
export const RECEIVE_IMPORT_SUCCESS = 'RECEIVE_IMPORT_SUCCESS';
export const RECEIVE_IMPORT_ERROR = 'RECEIVE_IMPORT_ERROR';
@@ -9,3 +13,5 @@ export const RECEIVE_IMPORT_ERROR = 'RECEIVE_IMPORT_ERROR';
export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS';
export const SET_FILTER = 'SET_FILTER';
+
+export const SET_IMPORT_TARGET = 'SET_IMPORT_TARGET';
diff --git a/app/assets/javascripts/import_projects/store/mutations.js b/app/assets/javascripts/import_projects/store/mutations.js
index ec62d0640ef..44fc3da4de5 100644
--- a/app/assets/javascripts/import_projects/store/mutations.js
+++ b/app/assets/javascripts/import_projects/store/mutations.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import * as types from './mutation_types';
+import { STATUSES } from '../constants';
export default {
[types.SET_FILTER](state, filter) {
@@ -12,48 +13,95 @@ export default {
[types.RECEIVE_REPOS_SUCCESS](
state,
- { importedProjects, providerRepos, incompatibleRepos, namespaces },
+ { importedProjects, providerRepos, incompatibleRepos = [] },
) {
+ // Normalizing structure to support legacy backend format
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/27370#note_379034091 for details
+
state.isLoadingRepos = false;
- state.importedProjects = importedProjects;
- state.providerRepos = providerRepos;
- state.incompatibleRepos = incompatibleRepos ?? [];
- state.namespaces = namespaces;
+ state.repositories = [
+ ...importedProjects.map(({ importSource, providerLink, importStatus, ...project }) => ({
+ importSource: {
+ id: `finished-${project.id}`,
+ fullName: importSource,
+ sanitizedName: project.name,
+ providerLink,
+ },
+ importStatus,
+ importedProject: project,
+ })),
+ ...providerRepos.map(project => ({
+ importSource: project,
+ importStatus: STATUSES.NONE,
+ importedProject: null,
+ })),
+ ...incompatibleRepos.map(project => ({
+ importSource: { ...project, incompatible: true },
+ importStatus: STATUSES.NONE,
+ importedProject: null,
+ })),
+ ];
},
[types.RECEIVE_REPOS_ERROR](state) {
state.isLoadingRepos = false;
},
- [types.REQUEST_IMPORT](state, repoId) {
- state.reposBeingImported.push(repoId);
+ [types.REQUEST_IMPORT](state, { repoId, importTarget }) {
+ const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
+ existingRepo.importStatus = STATUSES.SCHEDULING;
+ existingRepo.importedProject = {
+ fullPath: `/${importTarget.targetNamespace}/${importTarget.newName}`,
+ };
},
[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId }) {
- const existingRepoIndex = state.reposBeingImported.indexOf(repoId);
- if (state.reposBeingImported.includes(repoId))
- state.reposBeingImported.splice(existingRepoIndex, 1);
+ const { importStatus, ...project } = importedProject;
- const providerRepoIndex = state.providerRepos.findIndex(
- providerRepo => providerRepo.id === repoId,
- );
- state.providerRepos.splice(providerRepoIndex, 1);
- state.importedProjects.unshift(importedProject);
+ const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
+ existingRepo.importStatus = importStatus;
+ existingRepo.importedProject = project;
},
[types.RECEIVE_IMPORT_ERROR](state, repoId) {
- const repoIndex = state.reposBeingImported.indexOf(repoId);
- if (state.reposBeingImported.includes(repoId)) state.reposBeingImported.splice(repoIndex, 1);
+ const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
+ existingRepo.importStatus = STATUSES.NONE;
+ existingRepo.importedProject = null;
},
[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects) {
updatedProjects.forEach(updatedProject => {
- const existingProject = state.importedProjects.find(
- importedProject => importedProject.id === updatedProject.id,
- );
-
- Vue.set(existingProject, 'importStatus', updatedProject.importStatus);
+ const repo = state.repositories.find(p => p.importedProject?.id === updatedProject.id);
+ if (repo) {
+ repo.importStatus = updatedProject.importStatus;
+ }
});
},
+
+ [types.REQUEST_NAMESPACES](state) {
+ state.isLoadingNamespaces = true;
+ },
+
+ [types.RECEIVE_NAMESPACES_SUCCESS](state, namespaces) {
+ state.isLoadingNamespaces = false;
+ state.namespaces = namespaces;
+ },
+
+ [types.RECEIVE_NAMESPACES_ERROR](state) {
+ state.isLoadingNamespaces = false;
+ },
+
+ [types.SET_IMPORT_TARGET](state, { repoId, importTarget }) {
+ const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
+
+ if (
+ importTarget.targetNamespace === state.defaultTargetNamespace &&
+ importTarget.newName === existingRepo.importSource.sanitizedName
+ ) {
+ Vue.delete(state.customImportTargets, repoId);
+ } else {
+ Vue.set(state.customImportTargets, repoId, importTarget);
+ }
+ },
};
diff --git a/app/assets/javascripts/import_projects/store/state.js b/app/assets/javascripts/import_projects/store/state.js
index 0418d735b1d..8432a210955 100644
--- a/app/assets/javascripts/import_projects/store/state.js
+++ b/app/assets/javascripts/import_projects/store/state.js
@@ -1,17 +1,10 @@
export default () => ({
- reposPath: '',
- importPath: '',
- jobsPath: '',
- currentProjectId: '',
provider: '',
- currentUsername: '',
- importedProjects: [],
- providerRepos: [],
- incompatibleRepos: [],
+ repositories: [],
namespaces: [],
- reposBeingImported: [],
+ customImportTargets: {},
isLoadingRepos: false,
- canSelectNamespace: false,
+ isLoadingNamespaces: false,
ciCdOnly: false,
filter: '',
});
diff --git a/app/assets/javascripts/import_projects/utils.js b/app/assets/javascripts/import_projects/utils.js
new file mode 100644
index 00000000000..c2a2d5a607d
--- /dev/null
+++ b/app/assets/javascripts/import_projects/utils.js
@@ -0,0 +1,7 @@
+import { STATUSES } from './constants';
+
+// Will be expanded in future
+// eslint-disable-next-line import/prefer-default-export
+export function isProjectImportable(project) {
+ return project.importStatus === STATUSES.NONE && !project.importSource.incompatible;
+}
diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue
index ecd8acb449e..157e6f7b0ba 100644
--- a/app/assets/javascripts/incidents/components/incidents_list.vue
+++ b/app/assets/javascripts/incidents/components/incidents_list.vue
@@ -88,6 +88,7 @@ export default {
'projectPath',
'newIssuePath',
'incidentTemplateName',
+ 'incidentType',
'issuePath',
'publishedAvailable',
],
@@ -179,7 +180,13 @@ export default {
};
},
newIncidentPath() {
- return mergeUrlParams({ issuable_template: this.incidentTemplateName }, this.newIssuePath);
+ return mergeUrlParams(
+ {
+ issuable_template: this.incidentTemplateName,
+ 'issue[issue_type]': this.incidentType,
+ },
+ this.newIssuePath,
+ );
},
availableFields() {
return this.publishedAvailable
diff --git a/app/assets/javascripts/incidents/list.js b/app/assets/javascripts/incidents/list.js
index 3809742e32b..ac49ad63ad1 100644
--- a/app/assets/javascripts/incidents/list.js
+++ b/app/assets/javascripts/incidents/list.js
@@ -12,6 +12,7 @@ export default () => {
projectPath,
newIssuePath,
incidentTemplateName,
+ incidentType,
issuePath,
publishedAvailable,
} = domEl.dataset;
@@ -25,6 +26,7 @@ export default () => {
provide: {
projectPath,
incidentTemplateName,
+ incidentType,
newIssuePath,
issuePath,
publishedAvailable,
diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
index b1a4f3dccaf..4447a87777a 100644
--- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
+++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
@@ -1,5 +1,6 @@
<script>
import { GlTooltipDirective } from '@gitlab/ui';
+
import timeagoMixin from '../mixins/timeago';
import '../../lib/utils/datetime_utility';
@@ -28,6 +29,11 @@ export default {
default: '',
},
},
+ computed: {
+ timeAgo() {
+ return this.timeFormatted(this.time);
+ },
+ },
};
</script>
<template>
@@ -35,7 +41,7 @@ export default {
v-gl-tooltip.viewport="{ placement: tooltipPlacement }"
:class="cssClass"
:title="tooltipTitle(time)"
- v-text="timeFormatted(time)"
+ :datetime="time"
+ ><slot :timeAgo="timeAgo">{{ timeAgo }}</slot></time
>
- </time>
</template>
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 730853abc70..2200860a184 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -90,7 +90,7 @@ class Projects::IssuesController < Projects::ApplicationController
params[:issue] ||= ActionController::Parameters.new(
assignee_ids: ""
)
- build_params = issue_params.merge(
+ build_params = issue_create_params.merge(
merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
discussion_to_resolve: params[:discussion_to_resolve],
confidential: !!Gitlab::Utils.to_boolean(params[:issue][:confidential])
@@ -110,7 +110,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def create
- create_params = issue_params.merge(spammable_params).merge(
+ create_params = issue_create_params.merge(spammable_params).merge(
merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
discussion_to_resolve: params[:discussion_to_resolve]
)
@@ -293,6 +293,16 @@ class Projects::IssuesController < Projects::ApplicationController
] + [{ label_ids: [], assignee_ids: [], update_task: [:index, :checked, :line_number, :line_source] }]
end
+ def issue_create_params
+ create_params = %i[
+ issue_type
+ ]
+
+ params.require(:issue).permit(
+ *create_params
+ ).merge(issue_params)
+ end
+
def reorder_params
params.permit(:move_before_id, :move_after_id, :group_full_path)
end
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 98b0abc89e9..bceccc7063b 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -41,7 +41,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def diffs_metadata
diffs = @compare.diffs(diff_options)
- render json: DiffsMetadataSerializer.new(project: @merge_request.project)
+ render json: DiffsMetadataSerializer.new(project: @merge_request.project, current_user: current_user)
.represent(diffs, additional_attributes)
end
diff --git a/app/finders/concerns/merged_at_filter.rb b/app/finders/concerns/merged_at_filter.rb
index d908e9e1724..d2858ba2f88 100644
--- a/app/finders/concerns/merged_at_filter.rb
+++ b/app/finders/concerns/merged_at_filter.rb
@@ -11,7 +11,9 @@ module MergedAtFilter
mr_metrics_scope = mr_metrics_scope.merged_after(merged_after) if merged_after.present?
mr_metrics_scope = mr_metrics_scope.merged_before(merged_before) if merged_before.present?
- items.joins(:metrics).merge(mr_metrics_scope)
+ scope = items.joins(:metrics).merge(mr_metrics_scope)
+ scope = target_project_id_filter_on_metrics(scope) if Feature.enabled?(:improved_mr_merged_at_queries)
+ scope
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -22,4 +24,10 @@ module MergedAtFilter
def merged_before
params[:merged_before]
end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def target_project_id_filter_on_metrics(scope)
+ scope.where(MergeRequest.arel_table[:target_project_id].eq(MergeRequest::Metrics.arel_table[:target_project_id]))
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 9b2234853fb..b97e847c397 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -1,10 +1,6 @@
# frozen_string_literal: true
module ClustersHelper
- def has_multiple_clusters?
- true
- end
-
def create_new_cluster_label(provider: nil)
case provider
when 'aws'
@@ -31,7 +27,12 @@ module ClustersHelper
def js_cluster_form_data(cluster, can_edit)
{
enabled: cluster.enabled?.to_s,
- editable: can_edit.to_s
+ editable: can_edit.to_s,
+ environment_scope: cluster.environment_scope,
+ base_domain: cluster.base_domain,
+ application_ingress_external_ip: cluster.application_ingress_external_ip,
+ auto_devops_help_path: help_page_path('topics/autodevops/index'),
+ external_endpoint_help_path: help_page_path('user/clusters/applications.md', anchor: 'pointing-your-dns-at-the-external-endpoint')
}
end
diff --git a/app/helpers/projects/incidents_helper.rb b/app/helpers/projects/incidents_helper.rb
index 3b494ec2472..8cdd65fecb5 100644
--- a/app/helpers/projects/incidents_helper.rb
+++ b/app/helpers/projects/incidents_helper.rb
@@ -6,6 +6,7 @@ module Projects::IncidentsHelper
'project-path' => project.full_path,
'new-issue-path' => new_project_issue_path(project),
'incident-template-name' => 'incident',
+ 'incident-type' => 'incident',
'issue-path' => project_issues_path(project)
}
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index cab7ee6bc6b..1ce4903f8df 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -184,9 +184,8 @@ module ProjectsHelper
end
def autodeploy_flash_notice(branch_name)
- translation = _("Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}") %
- { branch_name: truncate(sanitize(branch_name)), link_to_autodeploy_doc: link_to_autodeploy_doc }
- translation.html_safe
+ html_escape(_("Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}")) %
+ { branch_name: tag.strong(truncate(sanitize(branch_name))), link_to_autodeploy_doc: link_to_autodeploy_doc }
end
def project_list_cache_key(project, pipeline_status: true)
@@ -353,14 +352,14 @@ module ProjectsHelper
description =
if share_with_group && share_with_members
- _("You can invite a new member to <strong>%{project_name}</strong> or invite another group.")
+ _("You can invite a new member to %{project_name} or invite another group.")
elsif share_with_group
- _("You can invite another group to <strong>%{project_name}</strong>.")
+ _("You can invite another group to %{project_name}.")
elsif share_with_members
- _("You can invite a new member to <strong>%{project_name}</strong>.")
+ _("You can invite a new member to %{project_name}.")
end
- description.html_safe % { project_name: project.name }
+ html_escape(description) % { project_name: tag.strong(project.name) }
end
def metrics_external_dashboard_url
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 2a67484ac8b..b2d4236ca6b 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -733,10 +733,6 @@ module Ci
end
end
- def update_legacy_status
- set_status(latest_builds_status.to_s)
- end
-
def protected_ref?
strong_memoize(:protected_ref) { project.protected_for?(git_ref) }
end
@@ -1089,12 +1085,6 @@ module Ci
end
end
- def latest_builds_status
- return 'failed' unless yaml_errors.blank?
-
- statuses.latest.slow_composite_status(project: project) || 'skipped'
- end
-
def keep_around_commits
return unless project
diff --git a/app/models/clusters/providers/aws.rb b/app/models/clusters/providers/aws.rb
index faf587fb83d..86869361ed8 100644
--- a/app/models/clusters/providers/aws.rb
+++ b/app/models/clusters/providers/aws.rb
@@ -5,6 +5,9 @@ module Clusters
class Aws < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include Clusters::Concerns::ProviderStatus
+ include IgnorableColumns
+
+ ignore_column :created_by_user_id, remove_with: '13.4', remove_after: '2020-08-22'
self.table_name = 'cluster_providers_aws'
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index f4b6b40a11f..c003a20f0fc 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -97,7 +97,11 @@ class NotificationSetting < ApplicationRecord
alias_method :fixed_pipeline?, :fixed_pipeline
def event_enabled?(event)
- respond_to?(event) && !!public_send(event) # rubocop:disable GitlabSecurity/PublicSend
+ # We override these two attributes, so we can't use read_attribute
+ return failed_pipeline if event.to_sym == :failed_pipeline
+ return fixed_pipeline if event.to_sym == :fixed_pipeline
+
+ has_attribute?(event) && !!read_attribute(event)
end
def owns_notification_email
diff --git a/app/serializers/diffs_metadata_entity.rb b/app/serializers/diffs_metadata_entity.rb
index b7024721ea9..8973f23734a 100644
--- a/app/serializers/diffs_metadata_entity.rb
+++ b/app/serializers/diffs_metadata_entity.rb
@@ -3,4 +3,23 @@
class DiffsMetadataEntity < DiffsEntity
unexpose :diff_files
expose :raw_diff_files, as: :diff_files, using: DiffFileMetadataEntity
+
+ expose :conflict_resolution_path do |_, options|
+ presenter(options[:merge_request]).conflict_resolution_path
+ end
+
+ expose :has_conflicts do |_, options|
+ options[:merge_request].cannot_be_merged?
+ end
+
+ expose :can_merge do |_, options|
+ options[:merge_request].can_be_merged_by?(request.current_user)
+ end
+
+ private
+
+ def presenter(merge_request)
+ @presenters ||= {}
+ @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: request.current_user) # rubocop: disable CodeReuse/Presenter
+ end
end
diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb
index e62315de5f9..2de6ed9fa1c 100644
--- a/app/services/issues/build_service.rb
+++ b/app/services/issues/build_service.rb
@@ -66,7 +66,7 @@ module Issues
def whitelisted_issue_params
base_params = [:title, :description, :confidential]
- admin_params = [:milestone_id]
+ admin_params = [:milestone_id, :issue_type]
if can?(current_user, :admin_issue, project)
params.slice(*(base_params + admin_params))
diff --git a/app/views/clusters/clusters/_gitlab_integration_form.html.haml b/app/views/clusters/clusters/_gitlab_integration_form.html.haml
index 5ad323d5557..87af74a398f 100644
--- a/app/views/clusters/clusters/_gitlab_integration_form.html.haml
+++ b/app/views/clusters/clusters/_gitlab_integration_form.html.haml
@@ -1,29 +1,3 @@
= form_for @cluster, url: clusterable.cluster_path(@cluster), as: :cluster, html: { class: 'js-cluster-integration-form' } do |field|
= form_errors(@cluster)
#js-cluster-integration-form{ data: js_cluster_form_data(@cluster, can?(current_user, :update_cluster, @cluster)) }
- .form-group
- %h5= s_('ClusterIntegration|Environment scope')
- = field.text_field :environment_scope, class: 'col-md-6 form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Environment scope')
- - environment_scope_url = help_page_path('user/project/clusters/index', anchor: 'base-domain')
- - environment_scope_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: environment_scope_url }
- .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster. %{environment_scope_start}More information%{environment_scope_end}").html_safe % { environment_scope_start: environment_scope_start, environment_scope_end: '</a>'.html_safe }
-
- .form-group
- %h5= s_('ClusterIntegration|Base domain')
- = field.text_field :base_domain, class: 'col-md-6 form-control js-select-on-focus', data: { qa_selector: 'base_domain_field' }
- .form-text.text-muted
- - auto_devops_url = help_page_path('topics/autodevops/index')
- - auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
- = s_('ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain.').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe }
- %span{ :class => ["js-ingress-domain-help-text", ("hide" unless @cluster.application_ingress_external_ip.present?)] }
- = s_('ClusterIntegration|Alternatively')
- %code{ :class => "js-ingress-domain-snippet" }
- = s_('ClusterIntegration|%{external_ip}.nip.io').html_safe % { external_ip: @cluster.application_ingress_external_ip }
- = s_('ClusterIntegration| can be used instead of a custom domain.')
- - custom_domain_url = help_page_path('user/clusters/applications.md', anchor: 'pointing-your-dns-at-the-external-endpoint')
- - custom_domain_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: custom_domain_url }
- = s_('ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}.').html_safe % { custom_domain_start: custom_domain_start, custom_domain_end: '</a>'.html_safe }
-
- - if can?(current_user, :update_cluster, @cluster)
- .form-group.gl-display-flex.gl-justify-content-end
- = field.submit _('Save changes'), class: 'btn btn-success', data: { qa_selector: 'save_changes_button'}
diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml
index 9bf1f0c61bb..b181b163375 100644
--- a/app/views/import/_githubish_status.html.haml
+++ b/app/views/import/_githubish_status.html.haml
@@ -6,6 +6,7 @@
#import-projects-mount-element{ data: { provider: provider, provider_title: provider_title,
can_select_namespace: current_user.can_select_namespace?.to_s,
ci_cd_only: has_ci_cd_only_params?.to_s,
+ namespaces_path: import_available_namespaces_path,
repos_path: url_for([:status, :import, provider, format: :json]),
jobs_path: url_for([:realtime_changes, :import, provider, format: :json]),
import_path: url_for([:import, provider, format: :json]),
diff --git a/app/views/profiles/chat_names/new.html.haml b/app/views/profiles/chat_names/new.html.haml
index 9246a443dec..2134ab2bec6 100644
--- a/app/views/profiles/chat_names/new.html.haml
+++ b/app/views/profiles/chat_names/new.html.haml
@@ -2,7 +2,7 @@
= _("Authorization required")
%main{ :role => "main" }
%p.h4
- = _("Authorize <strong>%{user}</strong> to use your account?").html_safe % { user: @chat_name_params[:chat_name] }
+ = html_escape(_("Authorize %{user} to use your account?")) % { user: tag.strong(@chat_name_params[:chat_name]) }
%hr
.actions
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 1319c58eb38..7d072ba5899 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -1,8 +1,5 @@
- breadcrumb_title _("Repository")
- page_title _("Edit"), @blob.path, @ref
-- unless Feature.enabled?(:monaco_blobs)
- - content_for :page_specific_javascripts do
- = page_specific_javascript_tag('lib/ace.js')
- if @conflict
.alert.alert-danger
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 2420c4a4bd5..48ffd80aa9c 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,8 +1,5 @@
- breadcrumb_title _("Repository")
- page_title _("New File"), @path.presence, @ref
-- unless Feature.enabled?(:monaco_blobs)
- - content_for :page_specific_javascripts do
- = page_specific_javascript_tag('lib/ace.js')
.editor-title-row
%h3.page-title.blob-new-page-title
diff --git a/app/views/projects/merge_requests/_widget.html.haml b/app/views/projects/merge_requests/_widget.html.haml
index 16b08cbf648..64b14f8889c 100644
--- a/app/views/projects/merge_requests/_widget.html.haml
+++ b/app/views/projects/merge_requests/_widget.html.haml
@@ -1,6 +1,3 @@
-- if @merge_request.source_branch_exists?
- = render "projects/merge_requests/how_to_merge"
-
= javascript_tag nonce: true do
:plain
window.gl = window.gl || {};
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 8e51280383f..e619bf84992 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -12,6 +12,9 @@
.merge-request{ data: { mr_action: mr_action, url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version } }
= render "projects/merge_requests/mr_title"
+ - if @merge_request.source_branch_exists?
+ = render "projects/merge_requests/how_to_merge"
+
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
= render "projects/merge_requests/mr_box"
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index fc69b390bde..8a01945ffac 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -25,4 +25,4 @@
= render 'destroy'
- else
.bs-callout.bs-callout-warning
- = s_('GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project\'s %{strong_start}Settings > General > Visibility%{strong_end} page.').html_safe % { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
+ = html_escape_once(s_('GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project\'s %{strong_start}Settings &gt; General &gt; Visibility%{strong_end} page.')).html_safe % { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
index 7557a00679d..9d81fda68cb 100644
--- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
@@ -1,5 +1,5 @@
-- pretty_name = @project&.full_name || _('<project name>')
-- run_actions_text = s_("ProjectService|Perform common operations on GitLab project: %{project_name}") % { project_name: pretty_name }
+- pretty_name = html_escape(@project&.full_name) || html_escape_once(_('&lt;project name&gt;')).html_safe
+- run_actions_text = html_escape(s_("ProjectService|Perform common operations on GitLab project: %{project_name}")) % { project_name: pretty_name }
%p= s_("ProjectService|To set up this service:")
%ul.list-unstyled.indent-list
@@ -21,7 +21,7 @@
.form-group
= label_tag :display_name, _('Display name'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
- = text_field_tag :display_name, "GitLab / #{pretty_name}", class: 'form-control form-control-sm', readonly: 'readonly'
+ = text_field_tag :display_name, "GitLab / #{pretty_name}".html_safe, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#display_name', class: 'input-group-text')
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index ff17a2c259c..8e3be5fa086 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -85,7 +85,7 @@
%p
- freeze_period_docs = help_page_path('user/project/releases/index', anchor: 'prevent-unintentional-releases-by-setting-a-deploy-freeze')
- freeze_period_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: freeze_period_docs }
- = s_('DeployFreeze|Specify times when deployments are not allowed for an environment. The <code>gitlab-ci.yml</code> file must be updated to make deployment jobs aware of the %{freeze_period_link_start}freeze period%{freeze_period_link_end}.').html_safe % { freeze_period_link_start: freeze_period_link_start, freeze_period_link_end: '</a>'.html_safe }
+ = html_escape(s_('DeployFreeze|Specify times when deployments are not allowed for an environment. The %{filename} file must be updated to make deployment jobs aware of the %{freeze_period_link_start}freeze period%{freeze_period_link_end}.')) % { freeze_period_link_start: freeze_period_link_start, freeze_period_link_end: '</a>'.html_safe, filename: tag.code('gitlab-ci.yml') }
- cron_syntax_url = 'https://crontab.guru/'
- cron_syntax_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: cron_syntax_url }
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 2e66ed318a0..86cd2923fac 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -83,4 +83,7 @@
= render_if_exists 'shared/issuable/remove_approver'
+- if issuable.respond_to?(:issue_type)
+ = form.hidden_field :issue_type
+
= form.hidden_field :lock_version
diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml
index 3801bbd6512..30a1f0febc3 100644
--- a/app/views/shared/issuable/form/_branch_chooser.html.haml
+++ b/app/views/shared/issuable/form/_branch_chooser.html.haml
@@ -17,7 +17,9 @@
= link_to _('Change branches'), mr_change_branches_path(issuable)
- elsif issuable.for_fork?
%code= issuable.target_project_path + ":"
- - unless issuable.new_record?
+ - if issuable.merged?
+ %code= target_title
+ - unless issuable.new_record? || issuable.merged?
%span.dropdown.gl-ml-2.d-inline-block
= form.hidden_field(:target_branch,
{ class: 'target_branch js-target-branch-select ref-name mw-xl',
diff --git a/changelogs/unreleased/198611-monaco_blobs-on-by-default.yml b/changelogs/unreleased/198611-monaco_blobs-on-by-default.yml
new file mode 100644
index 00000000000..e1866c87aaf
--- /dev/null
+++ b/changelogs/unreleased/198611-monaco_blobs-on-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Enabled monaco_blobs FF by default
+merge_request: 39441
+author:
+type: changed
diff --git a/changelogs/unreleased/219381-issue-type-param-when-creating-issues.yml b/changelogs/unreleased/219381-issue-type-param-when-creating-issues.yml
new file mode 100644
index 00000000000..bf356b947cb
--- /dev/null
+++ b/changelogs/unreleased/219381-issue-type-param-when-creating-issues.yml
@@ -0,0 +1,5 @@
+---
+title: Set Incident issue type when creating issue
+merge_request: 38760
+author:
+type: added
diff --git a/config/feature_flags/development/improved_mr_merged_at_queries.yml b/config/feature_flags/development/improved_mr_merged_at_queries.yml
new file mode 100644
index 00000000000..9e717991a7d
--- /dev/null
+++ b/config/feature_flags/development/improved_mr_merged_at_queries.yml
@@ -0,0 +1,7 @@
+---
+name: improved_mr_merged_at_queries
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39329
+rollout_issue_url:
+group: group::analytics
+type: development
+default_enabled: false
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
index 7a9b97dfefa..47d72c4c2e7 100644
--- a/db/fixtures/development/14_pipelines.rb
+++ b/db/fixtures/development/14_pipelines.rb
@@ -57,7 +57,7 @@ class Gitlab::Seeder::Pipelines
BUILDS.each { |opts| build_create!(pipeline, opts) }
EXTERNAL_JOBS.each { |opts| commit_status_create!(pipeline, opts) }
pipeline.update_duration
- pipeline.update_legacy_status
+ ::Ci::ProcessPipelineService.new(pipeline).execute
end
end
diff --git a/db/migrate/20200812112204_add_index_to_mr_metrics_target_project_id.rb b/db/migrate/20200812112204_add_index_to_mr_metrics_target_project_id.rb
new file mode 100644
index 00000000000..da483e7222c
--- /dev/null
+++ b/db/migrate/20200812112204_add_index_to_mr_metrics_target_project_id.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddIndexToMrMetricsTargetProjectId < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_merge_request_metrics_on_target_project_id_merged_at'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :merge_request_metrics, [:target_project_id, :merged_at], name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name(:merge_request_metrics, INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20200812112204 b/db/schema_migrations/20200812112204
new file mode 100644
index 00000000000..34ed55dc045
--- /dev/null
+++ b/db/schema_migrations/20200812112204
@@ -0,0 +1 @@
+4fb7fda59db193250ca393cae5561fa52d61e025bb93b10829f114ac7fef4cfa \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 25af1ab11d6..b10809bf792 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -19996,6 +19996,8 @@ CREATE INDEX index_merge_request_metrics_on_pipeline_id ON public.merge_request_
CREATE INDEX index_merge_request_metrics_on_target_project_id ON public.merge_request_metrics USING btree (target_project_id);
+CREATE INDEX index_merge_request_metrics_on_target_project_id_merged_at ON public.merge_request_metrics USING btree (target_project_id, merged_at);
+
CREATE UNIQUE INDEX index_merge_request_user_mentions_on_note_id ON public.merge_request_user_mentions USING btree (note_id) WHERE (note_id IS NOT NULL);
CREATE INDEX index_merge_requests_closing_issues_on_issue_id ON public.merge_requests_closing_issues USING btree (issue_id);
diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt
index a9fa0ca5db9..1301e8c4ca1 100644
--- a/doc/.vale/gitlab/spelling-exceptions.txt
+++ b/doc/.vale/gitlab/spelling-exceptions.txt
@@ -469,6 +469,7 @@ TypeScript
Twilio
Twitter
Ubuntu
+unapplied
unarchive
unarchived
unarchives
diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md
index 98a0af6ec04..2565c406f1e 100644
--- a/doc/administration/gitaly/praefect.md
+++ b/doc/administration/gitaly/praefect.md
@@ -1041,40 +1041,63 @@ strategy in the future.
## Primary Node Failure
-Praefect recovers from a failing primary Gitaly node by promoting a healthy secondary as the new primary. To minimize data loss, Praefect elects the secondary with the least unreplicated writes from the primary. There can still be some unreplicated writes, leading to data loss.
+Gitaly Cluster recovers from a failing primary Gitaly node by promoting a healthy secondary as the
+new primary.
+
+To minimize data loss, Gitaly Cluster:
+
+- Switches repositories that are outdated on the new primary to [read-only mode](#read-only-mode).
+- Elects the secondary with the least unreplicated writes from the primary to be the new primary.
+ Because there can still be some unreplicated writes, [data loss can occur](#check-for-data-loss).
### Read-only mode
-If the primary is not fully up to date, Praefect switches:
+> - Introduced in GitLab 13.0 as [generally available](https://about.gitlab.com/handbook/product/gitlab-the-product/#generally-available-ga).
+> - Between GitLab 13.0 and GitLab 13.2, read-only mode applied to the whole virtual storage and occurred whenever failover occurred.
+> - [In GitLab 13.3 and later](https://gitlab.com/gitlab-org/gitaly/-/issues/2862), read-only mode applies on a per-repository basis and only occurs if a new primary is out of date.
+
+When Gitaly Cluster switches to a new primary, repositories enter read-only mode if they are out of
+date. This can happen after failing over to an outdated secondary. Read-only mode eases data
+recovery efforts by preventing writes that may conflict with the unreplicated writes on other nodes.
+
+To enable writes again, an administrator can:
-- The virtual storage to read-only mode in GitLab 13.2 and earlier.
-- The affected repositories to read-only mode in
- [GitLab 13.3](https://gitlab.com/gitlab-org/gitaly/-/issues/2862) and later.
+1. [Check](#check-for-data-loss) for data loss.
+1. Attempt to [recover](#recover-missing-data) missing data.
+1. Either [enable writes](#enable-writes-or-accept-data-loss) in the virtual storage or
+ [accept data loss](#enable-writes-or-accept-data-loss) if necessary, depending on the version of
+ GitLab.
-This can happen after failing over to an outdated secondary. Read-only mode eases data
-recovery efforts by preventing writes that may conflict with the unreplicated writes.
+### Check for data loss
-To re-enable the repositories for writes, the administrator can attempt to
-[recover the missing data](#recover-missing-data) from an up-to-date replica. If
-recovering the data is not possible, the repository can be enabled for writes again by
-[accepting the data loss](#accept-data-loss).
+The Praefect `dataloss` sub-command identifies replicas that are likely to be outdated. This is
+useful for identifying potential data loss after a failover. The following parameters are
+available:
-### Checking for data loss
+- `-virtual-storage` that specifies which virtual storage to check. The default behavior is to
+ display outdated replicas of read-only repositories as they generally require administrator
+ action.
+- In GitLab 13.3 and later, `-partially-replicated` that specifies whether to display a list of
+ [outdated replicas of writable repositories](#outdated-replicas-of-writable-repositories).
-The Praefect `dataloss` sub-command identifies replicas that are likely to be outdated. This is useful for identifying potential data loss after a failover.
+NOTE: **Note:**
+`dataloss` is still in beta and the output format is subject to change.
+
+To check for outdated replicas of read-only repositories, run:
```shell
-sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss [-virtual-storage <virtual-storage>] [-writable]
+sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss [-virtual-storage <virtual-storage>]
```
-- `-virtual-storage` specifies which virtual storage to check. Every configured virtual storage is checked if none is specified.
-- `-writable` specifies whether to report outdated replicas of writable repositories. Repository is writable if the primary has the latest changes. Secondaries might be temporarily outdated while they are waiting to replicate the latest changes. To reduce the noise, default behavior is to display outdated replicas of read-only repositories as they generally require administrator action.
+Every configured virtual storage is checked if none is specified:
```shell
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss
```
-The number of potentially unapplied changes to repositories are listed for each replica. Listed repositories might have the latest changes but it is not guaranteed. Only outdated replicas of read-only repositories are listed by default.
+The number of potentially unapplied changes to repositories is listed for each replica. Listed
+repositories might have the latest changes but it is not guaranteed. Only outdated replicas of
+read-only repositories are listed by default. For example:
```shell
Virtual storage: default
@@ -1085,7 +1108,29 @@ Virtual storage: default
gitaly-3 is behind by 2 changes or less
```
-Set the `-writable` flag also list the outdated replicas of writable repositories.
+A confirmation is printed out when every repository is writable. For example:
+
+```shell
+Virtual storage: default
+ Primary: gitaly-1
+ All repositories are writable!
+```
+
+#### Outdated replicas of writable repositories
+
+> [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/3019) in GitLab 13.3.
+
+To also list information for outdated replicas of writable repositories, use the
+`-partially-replicated` parameter.
+
+A repository is writable if the primary has the latest changes. Secondaries might be temporarily
+outdated while they are waiting to replicate the latest changes.
+
+```shell
+sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss [-virtual-storage <virtual-storage>] [-partially-replicated]
+```
+
+Example output:
```shell
Virtual storage: default
@@ -1098,15 +1143,8 @@ Virtual storage: default
gitaly-2 is behind by 1 change or less
```
-A confirmation is printed out when every repository is writable.
-
-```shell
-Virtual storage: default
- Primary: gitaly-1
- All repositories are writable!
-```
-
-With the `-writable` flag set, a confirmation is printed out if every replica is fully up to date.
+With the `-partially-replicated` flag set, a confirmation is printed out if every replica is fully up to date.
+For example:
```shell
Virtual storage: default
@@ -1114,17 +1152,15 @@ Virtual storage: default
All repositories are up to date!
```
-NOTE: **Note:**
-`dataloss` is still in beta and the output format is subject to change.
-
-### Checking repository checksums
+### Check repository checksums
To check a project's repository checksums across on all Gitaly nodes, run the
[replicas Rake task](../raketasks/praefect.md#replica-checksums) on the main GitLab node.
### Recover missing data
-The Praefect `reconcile` sub-command can be used to recover unreplicated changes from another replica. The source must be on a later generation than the target storage.
+The Praefect `reconcile` sub-command can be used to recover unreplicated changes from another replica.
+The source must be on a later version than the target storage.
```shell
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml reconcile -virtual <virtual-storage> -reference <up-to-date-storage> -target <outdated-storage> -f
@@ -1132,30 +1168,30 @@ sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.t
Refer to [Backend Node Recovery](#backend-node-recovery) section for more details on the `reconcile` sub-command.
-### Accept data loss
+### Enable writes or accept data loss
Praefect provides the following subcommands to re-enable writes:
-- In GitLab 13.2 and earlier, `enable-writes` to re-enable virtual storage for writes
- after data recovery attempts.
+- In GitLab 13.2 and earlier, `enable-writes` to re-enable virtual storage for writes after data
+ recovery attempts.
```shell
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml enable-writes -virtual-storage <virtual-storage>
```
-- [In GitLab 13.3](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2415) and
- later, `accept-dataloss` to accept data loss and re-enable writes for repositories
- after data recovery attempts have failed. Accepting data loss causes current version
- of the repository on the authoritative storage to be considered latest. Other storages
- are brought up to date with the authoritative storage by scheduling replication jobs.
+- [In GitLab 13.3](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2415) and later,
+ `accept-dataloss` to accept data loss and re-enable writes for repositories after data recovery
+ attempts have failed. Accepting data loss causes current version of the repository on the
+ authoritative storage to be considered latest. Other storages are brought up to date with the
+ authoritative storage by scheduling replication jobs.
```shell
sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml accept-dataloss -virtual-storage <virtual-storage> -repository <relative-path> -authoritative-storage <storage-name>
```
CAUTION: **Caution:**
-`accept-dataloss` causes permanent data loss by overwriting other versions of the
-repository. Data recovery efforts must be performed before using it.
+`accept-dataloss` causes permanent data loss by overwriting other versions of the repository. Data
+[recovery efforts](#recover-missing-data) must be performed before using it.
## Backend Node Recovery
diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md
index 924bb705737..c036845e16e 100644
--- a/doc/user/application_security/secret_detection/index.md
+++ b/doc/user/application_security/secret_detection/index.md
@@ -164,6 +164,7 @@ Secret Detection can be customized by defining available variables:
|-------------------------|---------------|-------------|
| `SECRET_DETECTION_COMMIT_FROM` | - | The commit a Gitleaks scan starts at. |
| `SECRET_DETECTION_COMMIT_TO` | - | The commit a Gitleaks scan ends at. |
+| `SECRET_DETECTION_EXCLUDED_PATHS` | `spec, test, tests, tmp` | Exclude vulnerabilities from output based on the paths. This is a comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec` ). Parent directories will also match patterns. |
| `SECRET_DETECTION_HISTORIC_SCAN` | false | Flag to enable a historic Gitleaks scan. |
### Logging level
diff --git a/doc/user/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md
index af04fcd1b7f..14d68b9b156 100644
--- a/doc/user/project/merge_requests/squash_and_merge.md
+++ b/doc/user/project/merge_requests/squash_and_merge.md
@@ -92,7 +92,7 @@ squashing can itself be considered equivalent to rebasing.
## Squash Commits Options
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17613) in GitLab 13.2.
-> - It's deployed behind a feature flag, enabled by default.
+> - It's deployed behind a feature flag, enabled by default in GitLab 13.3.
> - It's enabled on GitLab.com.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrator can opt to [disable it](#enable-or-disable-squash-commit-options-core-only). **(CORE ONLY)**
diff --git a/lib/api/helpers/packages/basic_auth_helpers.rb b/lib/api/helpers/packages/basic_auth_helpers.rb
index cef5db10d0f..e35a8712131 100644
--- a/lib/api/helpers/packages/basic_auth_helpers.rb
+++ b/lib/api/helpers/packages/basic_auth_helpers.rb
@@ -13,10 +13,6 @@ module API
include Constants
- def find_personal_access_token
- find_personal_access_token_from_http_basic_auth
- end
-
def unauthorized_user_project
@unauthorized_user_project ||= find_project(params[:id])
end
diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
new file mode 100644
index 00000000000..e87f0f28d01
--- /dev/null
+++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
@@ -0,0 +1,146 @@
+stages:
+ - build
+ - test
+ - deploy
+ - fuzz
+
+variables:
+ FUZZAPI_PROFILE: Quick
+ FUZZAPI_VERSION: latest
+ FUZZAPI_CONFIG: "/app/.gitlab-api-fuzzing.yml"
+ FUZZAPI_TIMEOUT: 30
+ FUZZAPI_REPORT: gl-api-fuzzing-report.xml
+ #
+ FUZZAPI_D_NETWORK: testing-net
+ #
+ # Wait up to 5 minutes for API Fuzzer and target url to become
+ # available (non 500 response to HTTP(s))
+ FUZZAPI_SERVICE_START_TIMEOUT: "300"
+ #
+
+apifuzzer_fuzz:
+ stage: fuzz
+ image: docker:19.03.12
+ variables:
+ DOCKER_DRIVER: overlay2
+ DOCKER_TLS_CERTDIR: ""
+ FUZZAPI_PROJECT: $CI_PROJECT_PATH
+ FUZZAPI_API: http://apifuzzer:80
+ allow_failure: true
+ rules:
+ - if: $API_FUZZING_DISABLED
+ when: never
+ - if: $API_FUZZING_DISABLED_FOR_DEFAULT_BRANCH &&
+ $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
+ when: never
+ - if: $FUZZAPI_HAR == null &&
+ $FUZZAPI_OPENAPI == null &&
+ $FUZZAPI_D_WORKER_IMAGE == null
+ when: never
+ - if: $FUZZAPI_D_WORKER_IMAGE == null &&
+ $FUZZAPI_TARGET_URL == null
+ when: never
+ - if: $GITLAB_FEATURES =~ /\bapi_fuzzing\b/
+ services:
+ - docker:19.03.12-dind
+ script:
+ #
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+ #
+ - docker network create --driver bridge $FUZZAPI_D_NETWORK
+ #
+ # Run user provided pre-script
+ - sh -c "$FUZZAPI_PRE_SCRIPT"
+ #
+ # Start peach testing engine container
+ - |
+ docker run -d \
+ --name apifuzzer \
+ --network $FUZZAPI_D_NETWORK \
+ -e Proxy:Port=8000 \
+ -e TZ=America/Los_Angeles \
+ -e FUZZAPI_API=http://127.0.0.1:80 \
+ -e FUZZAPI_PROJECT \
+ -e FUZZAPI_PROFILE \
+ -e FUZZAPI_CONFIG \
+ -e FUZZAPI_REPORT \
+ -e FUZZAPI_HAR \
+ -e FUZZAPI_OPENAPI \
+ -e FUZZAPI_TARGET_URL \
+ -e FUZZAPI_OVERRIDES_FILE \
+ -e FUZZAPI_OVERRIDES_ENV \
+ -e FUZZAPI_OVERRIDES_CMD \
+ -e FUZZAPI_OVERRIDES_INTERVAL \
+ -e FUZZAPI_TIMEOUT \
+ -e FUZZAPI_VERBOSE \
+ -e FUZZAPI_SERVICE_START_TIMEOUT \
+ -e GITLAB_FEATURES \
+ -v $CI_PROJECT_DIR:/app \
+ -p 80:80 \
+ -p 8000:8000 \
+ -p 514:514 \
+ --restart=no \
+ registry.gitlab.com/gitlab-org/security-products/analyzers/api-fuzzing-src:${FUZZAPI_VERSION}-engine
+ #
+ # Start target container
+ - |
+ if [ "$FUZZAPI_D_TARGET_IMAGE" != "" ]; then \
+ docker run -d \
+ --name target \
+ --network $FUZZAPI_D_NETWORK \
+ $FUZZAPI_D_TARGET_ENV \
+ $FUZZAPI_D_TARGET_PORTS \
+ $FUZZAPI_D_TARGET_VOLUME \
+ --restart=no \
+ $FUZZAPI_D_TARGET_IMAGE \
+ ; fi
+ #
+ # Start worker container
+ - |
+ if [ "$FUZZAPI_D_WORKER_IMAGE" != "" ]; then \
+ echo "Starting worker image $FUZZAPI_D_WORKER_IMAGE" \
+ docker run \
+ --name worker \
+ --network $FUZZAPI_D_NETWORK \
+ -e FUZZAPI_API=http://apifuzzer:80 \
+ -e FUZZAPI_PROJECT \
+ -e FUZZAPI_PROFILE \
+ -e FUZZAPI_AUTOMATION_CMD \
+ -e FUZZAPI_CONFIG \
+ -e FUZZAPI_REPORT \
+ -e CI_COMMIT_BRANCH=${CI_COMMIT_BRANCH} \
+ $FUZZAPI_D_WORKER_ENV \
+ $FUZZAPI_D_WORKER_PORTS \
+ $FUZZAPI_D_WORKER_VOLUME \
+ --restart=no \
+ $FUZZAPI_D_WORKER_IMAGE \
+ ; fi
+ #
+ # Wait for testing to complete if api fuzzer is scanning
+ - if [ "$FUZZAPI_HAR$FUZZAPI_OPENAPI" != "" ]; then echo "Waiting for API Fuzzer to exit"; docker wait apifuzzer; fi
+ #
+ # Run user provided pre-script
+ - sh -c "$FUZZAPI_POST_SCRIPT"
+ #
+ after_script:
+ #
+ # Shutdown all containers
+ - echo "Stopping all containers"
+ - if [ "$FUZZAPI_D_TARGET_IMAGE" != "" ]; then docker stop target; fi
+ - if [ "$FUZZAPI_D_WORKER_IMAGE" != "" ]; then docker stop worker; fi
+ - docker stop apifuzzer
+ #
+ # Save docker logs
+ - docker logs apifuzzer &> gl-api_fuzzing-logs.log
+ - if [ "$FUZZAPI_D_TARGET_IMAGE" != "" ]; then docker logs target &> gl-api_fuzzing-target-logs.log; fi
+ - if [ "$FUZZAPI_D_WORKER_IMAGE" != "" ]; then docker logs worker &> gl-api_fuzzing-worker-logs.log; fi
+ #
+ artifacts:
+ when: always
+ paths:
+ - ./gl-api_fuzzing*.log
+ - ./gl-api_fuzzing*.zip
+ reports:
+ junit: $FUZZAPI_REPORT
+
+# end
diff --git a/lib/gitlab/config_checker/external_database_checker.rb b/lib/gitlab/config_checker/external_database_checker.rb
index af828acb9c0..dfcdbdf39e0 100644
--- a/lib/gitlab/config_checker/external_database_checker.rb
+++ b/lib/gitlab/config_checker/external_database_checker.rb
@@ -9,35 +9,41 @@ module Gitlab
notices = []
unless Gitlab::Database.postgresql_minimum_supported_version?
+ string_args = {
+ pg_version_current: Gitlab::Database.version,
+ pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION,
+ pg_requirements_url_open: '<a href="https://docs.gitlab.com/ee/install/requirements.html#database">'.html_safe,
+ pg_requirements_url_close: '</a>'.html_safe
+ }
+
notices <<
{
type: 'warning',
- message: _('You are using PostgreSQL %{pg_version_current}, but PostgreSQL ' \
+ message: html_escape(_('You are using PostgreSQL %{pg_version_current}, but PostgreSQL ' \
'%{pg_version_minimum} is required for this version of GitLab. ' \
'Please upgrade your environment to a supported PostgreSQL version, ' \
- 'see %{pg_requirements_url} for details.') % {
- pg_version_current: Gitlab::Database.version,
- pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION,
- pg_requirements_url: '<a href="https://docs.gitlab.com/ee/install/requirements.html#database">database requirements</a>'
- }
+ 'see %{pg_requirements_url_open}database requirements%{pg_requirements_url_close} for details.')) % string_args
}
end
if Gitlab::Database.postgresql_upcoming_deprecation? && Gitlab::Database.within_deprecation_notice_window?
upcoming_deprecation = Gitlab::Database::UPCOMING_POSTGRES_VERSION_DETAILS
+ string_args = {
+ pg_version_upcoming: upcoming_deprecation[:pg_version_minimum],
+ gl_version_upcoming: upcoming_deprecation[:gl_version],
+ gl_version_upcoming_date: upcoming_deprecation[:gl_version_date],
+ pg_version_upcoming_url_open: "<a href=\"#{upcoming_deprecation[:url]}\">".html_safe,
+ pg_version_upcoming_url_close: '</a>'.html_safe
+ }
+
notices <<
{
type: 'warning',
- message: _('Note that PostgreSQL %{pg_version_upcoming} will become the minimum required ' \
+ message: html_escape(_('Note that PostgreSQL %{pg_version_upcoming} will become the minimum required ' \
'version in GitLab %{gl_version_upcoming} (%{gl_version_upcoming_date}). Please ' \
'consider upgrading your environment to a supported PostgreSQL version soon, ' \
- 'see <a href="%{pg_version_upcoming_url}">the related epic</a> for details.') % {
- pg_version_upcoming: upcoming_deprecation[:pg_version_minimum],
- gl_version_upcoming: upcoming_deprecation[:gl_version],
- gl_version_upcoming_date: upcoming_deprecation[:gl_version_date],
- pg_version_upcoming_url: upcoming_deprecation[:url]
- }
+ 'see %{pg_version_upcoming_url_open}the related epic%{pg_version_upcoming_url_close} for details.')) % string_args
}
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 43d98d19d87..dfba68ce899 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -43,7 +43,7 @@ module Gitlab
# Initialize gon.features with any flags that should be
# made globally available to the frontend
push_frontend_feature_flag(:snippets_vue, default_enabled: true)
- push_frontend_feature_flag(:monaco_blobs, default_enabled: false)
+ push_frontend_feature_flag(:monaco_blobs, default_enabled: true)
push_frontend_feature_flag(:monaco_ci, default_enabled: false)
push_frontend_feature_flag(:snippets_edit_vue, default_enabled: false)
push_frontend_feature_flag(:webperf_experiment, default_enabled: false)
diff --git a/lib/gitlab/i18n/html_todo.yml b/lib/gitlab/i18n/html_todo.yml
index fb5a44f9b82..1ad036f79e2 100644
--- a/lib/gitlab/i18n/html_todo.yml
+++ b/lib/gitlab/i18n/html_todo.yml
@@ -16,14 +16,6 @@
# why this change has been made.
#
-"<project name>":
- translations:
- - "<название проекта>"
- - "<project name>"
- - "<proje adı>"
- - "<naziv projekta>"
- - "<ім’я проєкту>"
- - "<프로젝트 이름>"
" or <!merge request id>":
translations:
- " ወይም <!merge request id>"
@@ -114,23 +106,6 @@
- "Vous êtes sur le point de supprimer ce badge. Les badges supprimés <strong>ne peuvent pas</strong> être restaurés."
- "Va a eliminar esta insignia. Las insignias eliminadas <strong>no se pueden</strong> restaurar."
- "Bu rozeti sileceksiniz. Silinen rozetler geri <strong>yüklenemez</strong>."
-"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":
- plural_id:
- translations:
- - "分支 <strong>%{branch_name}</strong> 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"
- - "O branch <strong>%{branch_name}</strong> foi criado. Para configurar o deploy automático, selecione um modelo de Yaml do GitLab CI e commit suas mudanças. %{link_to_autodeploy_doc}"
- - "<strong>%{branch_name}</strong> ブランチが作成されました。自動デプロイを設定するには、GitLab CI Yaml テンプレートを選択して、変更をコミットしてください。 %{link_to_autodeploy_doc}"
- - "La branch <strong>%{branch_name}</strong> è stata creata. Per impostare un rilascio automatico scegli un template CI di Gitlab e committa le tue modifiche %{link_to_autodeploy_doc}"
- - "O ramo <strong>%{branch_name}</strong> foi criado. Para configurar a implantação automática, seleciona um modelo de Yaml do GitLab CI e envia as tuas alterações. %{link_to_autodeploy_doc}"
- - "Ветка <strong>%{branch_name}</strong> создана. Для настройки автоматического развертывания выберите YAML-шаблон для GitLab CI и зафиксируйте свои изменения. %{link_to_autodeploy_doc}"
- - "已创建分支 <strong>%{branch_name}</strong> 。如需设置自动部署, 请选择合适的 GitLab CI Yaml 模板并提交更改。%{link_to_autodeploy_doc}"
- - "Гілка <strong>%{branch_name}</strong> створена. Для настройки автоматичного розгортання виберіть GitLab CI Yaml-шаблон і закомітьте зміни. %{link_to_autodeploy_doc}"
- - "Клонът <strong>%{branch_name}</strong> беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}"
- - "Branch <strong>%{branch_name}</strong> wurde erstellt. Um die automatische Bereitstellung einzurichten, wähle eine GitLab CI Yaml Vorlage und committe deine Änderungen. %{link_to_autodeploy_doc}"
- - "<strong>%{branch_name}</strong> 브랜치가 생성되었습니다. 자동 배포를 설정하려면 GitLab CI Yaml 템플릿을 선택하고 변경 사항을 적용하십시오. %{link_to_autodeploy_doc}"
- - "La branĉo <strong>%{branch_name}</strong> estis kreita. Por agordi aŭtomatan disponigadon, bonvolu elekti Yaml-ŝablonon por GitLab CI kaj enmeti viajn ŝanĝojn. %{link_to_autodeploy_doc}"
- - "La branche <strong>%{branch_name}</strong> a été créée. Pour mettre en place le déploiement automatisé, sélectionnez un modèle de fichier YAML pour l’intégration continue (CI) de GitLab, et validez les modifications. %{link_to_autodeploy_doc}"
- - "La rama <strong>%{branch_name}</strong> fue creada. Para configurar el auto despliegue, escoge una plantilla Yaml para GitLab CI y envía tus cambios. %{link_to_autodeploy_doc}"
"ClusterIntegration| This will permanently delete the following resources: <ul> <li>All installed applications and related resources</li> <li>The <code>gitlab-managed-apps</code> namespace</li> <li>Any project namespaces</li> <li><code>clusterroles</code></li> <li><code>clusterrolebindings</code></li> </ul>":
plural_id:
translations:
@@ -161,53 +136,10 @@
- "Залежить від %d <strong>закритих</strong> запитів на злиття."
- "<strong>%d kapanan</strong> birleştirme isteğine bağlıdır."
- "<strong>%d kapanan</strong> birleştirme isteğine bağlıdır."
-"Don't worry, you can access this tour by clicking on the help icon in the top right corner and choose <strong>Learn GitLab</strong>.":
- plural_id:
- translations:
- - "心配しないでください。右上にあるヘルプアイコンをクリックして、このツアーにアクセスし、 <strong> GitLabを学ぶ </strong>を選択してください。"
- - "Не беспокойтесь, вы можете получить доступ к этому туру, нажав на значок справа в верхнем углу и выберите <strong>Узнать GitLab</strong>."
- - "不用担心,您可以通过单击右上角的帮助图标来访问此导览,然后选择 <strong>Learn GitLab</strong>。"
- - "Не хвилюйтеся, ви завжди зможете отримати доступ до цієї екскурсії, натиснувши кнопку довідки у верхньому правому куті і вибравши <strong>Дізнатися про GitLab</strong>."
- - "No se preocupe, puede acceder a este visita haciendo clic sobre el ícono de ayuda situado en la esquina superior derecha y seleccionando <strong>aprender GitLab</strong>."
-"Example: <code>acme.com,acme.co.in,acme.uk</code>.":
- plural_id:
- translations:
-"Git LFS objects will be synced in pull mirrors if LFS is %{docs_link_start}enabled for the project%{docs_link_end}. They will <strong>not</strong> be synced in push mirrors.":
- plural_id:
- translations:
- - "Objetos Git de LFS serão sincronizados em espelhos de pull se o LFS for %{docs_link_start}ativado para o projeto%{docs_link_end}. Eles <strong>não</strong> serão sincronizados em espelhos de push."
- - "LFSが %{docs_link_start} このプロジェクトで有効になっている %{docs_link_end} 場合にGit LFS オブジェクト プルミラーに同期されます。これらはプッシュミラーに同期 <strong>しません</strong> 。"
- - "Объекты GIt LFS будут синхронизироваться в pull-зеркала, если LFS %{docs_link_start} доступно для этого проекта%{docs_link_end}. Они <strong>не</strong> будут синхронизироваться в push-зеркала."
- - "如%{docs_link_start}为项目启用%{docs_link_end}了LFS,则Git LFS对象将在拉取镜像时同步, 但<strong>不会</strong>在推送镜像时同步。"
- - "Об’єкти Git LFS будуть синхронізуватися у pull-дзеркалах якщо LFS %{docs_link_start}увімкнено для проекту%{docs_link_end}. Вони <strong>не</strong> будуть синхронізуватися для push-дзеркал."
- - "Los objetos Git LFS se sincronizarán en los servidores de replica si LFS está %{docs_link_start}habilitado para el proyecto%{docs_link_end}. <strong>no</strong> se sincronizarán en servidores espejos de push."
-"GitLabPages|Access Control is enabled for this Pages website; only authorized users will be able to access it. To make your website publicly available, navigate to your project's %{strong_start}Settings > General > Visibility%{strong_end} and select %{strong_start}Everyone%{strong_end} in pages section. Read the %{link_start}documentation%{link_end} for more information.":
- plural_id:
- translations:
- - "此Pages网站已启用访问控制;只有经过授权的用户才能访问它。如果要公开您的网站,请转到你的项目的%{strong_start}设置 > 常规 > 可见性%{strong_end}并在Pages部分选择%{strong_start}所有人%{strong_end}。请查阅%{link_start}文档%{link_end}以获取更多信息。"
- - "Контроль доступу увімкнено для цього сайту Pages; лише авторизовані користувачі зможуть отримати доступ до нього. Для того, щоб зробити його загальнодоступним, перейдіть у вашому проєкті до %{strong_start}Налаштування > Загальні > Видимість%{strong_end} та виберіть%{strong_start}Будь-хто%{strong_end} у розділі pages. Перегляньте %{link_start}документацію%{link_end} для додаткової інформації."
-"GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project's %{strong_start}Settings > General > Visibility%{strong_end} page.":
- plural_id:
- translations:
- - "GitLab Pagesはこのプロジェクトでは無効になっています。 プロジェクトの%{strong_start} 設定> 全般> 可視性%{strong_end}ページで有効にできます。"
- - "GitLab Pages отключены для этого проекта. Вы можете включить в поле %{strong_start}Настройки > Общие > Видимость%{strong_end} вашего проекта."
- - "此项目禁用GitLab Pages。您可以在您的项目的%{strong_start}设置 > 常规 > 可见性%{strong_end} 页面启用。"
- - "GitLab Pages вимкнено для цього проєкту. Ви можете їх увімкнути перейшовши на сторінку проєкту %{strong_start}Налаштування > Загальні > Видимість%{strong_end}."
- - "Las páginas de GitLab están deshabilitadas para este proyecto. Puede habilitarlas en los ajustes %{strong_start} de su proyecto > General > Visibilidad%{strong_end}."
"Go to <strong>Issues</strong> > <strong>Boards</strong> to access your personalized learning issue board.":
plural_id:
translations:
- "转至<strong>议题</strong> > <strong>看板</strong>访问您的个性化学习议题看板。"
-"Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.":
- plural_id:
- translations:
- - "Inclua o nome de usuário no URL, se necessário: <code>https: //username@gitlab.company.com/group/project.git</code>."
- - "必要な場合、URL にユーザー名を含めます <code>https://username@gitlab.company.com/group/project.git</code>。"
- - "如果需要,请在URL中包含用户名: <code>https://username@gitlab.company.com/group/project.git</code>。"
- - "Якщо необхідно додайте ім'я користувача в URL: <code>https: //username@gitlab.company.com/group/project.git</code>."
- - "Füge bei Bedarf den Benutzernamen in die URL ein: <code>https://benutzername@gitlab.company.com/group/project.git</code>."
- - "Si besoin, ajouter le nom d’utilisateur dans l’URL : <code>https: //username@gitlab.company.com/group/project.git</code>."
- - "Incluya el nombre de usuario en la URL en caso de que sea necesario: <code>https://nombredeusuario@gitlab.empresa.com/grupo/proyecto.git</code>."
"Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>":
plural_id:
translations:
@@ -231,27 +163,6 @@
- "%{issuableDisplayName} sperren? Es werden nur noch <strong>Projektmitglieder</strong> kommentieren können."
- "Verrouiller ce·t·te %{issuableDisplayName} ? Seuls les <strong>membres du projet</strong> seront en mesure de commenter."
- "¿Bloquear este %{issuableDisplayName}? Sólo los <strong>miembros del proyecto</strong> podrán comentar."
-"Members can be added by project <i>Maintainers</i> or <i>Owners</i>":
- plural_id:
- translations:
- - "Os membros podem ser adicionados pelos <i>mantenedores</i> ou <i>proprietários</i> do projeto"
- - "プロジェクト毎の<i>メンテナー</i>または<i>オーナー</i>がメンバーを追加することができます"
- - "Os membros podem ser adicionados ao projeto <i>Responsáveis</i> ou <i>Proprietários</i>"
- - "Участники могут быть добавлены в проект <i>Сопровождающим</i> или <i>Владельцем</i>"
- - "项目 <i>维护者</i> 或 <i>所有者</i>可以添加成员"
- - "Учасники можуть будуть додані <i>Керівниками</i> або <i>Власниками</i> проекту"
- - "Los miembros se pueden añadir por proyecto <i>Mantenedores</i> o <i>Propietarios</i>"
- - "Üyeler, <i>Sorumlu</i> veya <i>Sahipleri</i> tarafından eklenebilir"
-"Members of <strong>%{project_name}</strong>":
- plural_id:
- translations:
- - "Membros de <strong>%{project_name}</strong>"
- - "<strong>%{project_name}</strong>のメンバー"
- - "Участники <strong>%{project_name}</strong>"
- - "<strong>%{project_name}</strong>的成员"
- - "Учасники <strong>%{project_name}</strong>"
- - "Miembros de <strong>%{project_name}</strong>"
- - "<strong>%{project_name}</strong> projesinin üyeleri"
"PrometheusService|<p class=\\\"text-tertiary\\\">No <a href=\\\"%{docsUrl}\\\">common metrics</a> were found</p>":
plural_id:
translations:
@@ -264,22 +175,6 @@
- "<p class=\\\"text-tertiary\\\"><a href=\\\"%{docsUrl}\\\">공통 메트릭스</a>가 발견되지 않았습니다.</p>"
- "<p class=\\\"text-tertiary\\\">Aucune <a href=\\\"%{docsUrl}\\\">métrique commune</a> trouvée</p>"
- "<p class=\\\"text-tertiary\\\">No se han encontrado<a href=\\\"%{docsUrl}\\\">métricas comunes</a> </p>"
-"The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>.":
- plural_id:
- translations:
- - "リポジトリは <code>http://</code>、<code>https://</code>、<code>ssh://</code>、<code>git://</code>のいずれかでアクセスできる必要があります。"
- - "Репозиторий должен быть доступен через <code>http://</code>, <code>https://</code>, <code>ssh://</code> или <code>git://</code>."
- - "该仓库必须可通过 <code>http://</code>,<code>https://</code>,<code>ssh://</code> 或 <code>git://</code>进行访问。"
- - "Репозиторій має бути доступним через <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>."
- - "El repositorio debe ser accesible a través de <code>http://</code>, <code>https://</code>, <code>ssh://</code> y <code>git://</code>."
-"This group, including all subgroups, projects and git repositories, will only be reachable from the specified IP address range. Multiple addresses are supported with comma delimiters.<br>Example: <code>192.168.0.0/24,192.168.1.0/24</code>. %{read_more_link}.":
- plural_id:
- translations:
- - "すべてのサブグループ、プロジェクト、gitリポジトリを含むこのグループは、指定されたIPアドレス範囲からのみ到達できます。コンマで区切ることで複数のアドレスを指定できます。<br>例: <code>192.168.0.0/24,192.168.1.0/24</code> %{read_more_link}。"
- - "Эта группа, включая все подгруппы, проекты и репозитории git, будет доступна только из указанного диапазона IP адресов. Несколько диапазонов адресов поддерживаются через разделитель - запятую.<br>Пример: <code>192.168.0/24,192.168.1.0/24</code>. %{read_more_link}."
- - "此群组,包括所有子群组、项目和git仓库,只能从指定的IP地址范围中访问。支持用逗号分隔符分隔的多个地址列表。例如: <code>192.168.0.0/24,168.1.0/24</code>. %{read_more_link}。"
- - "Ця група разом із усіма її підгрупами, проектами та репозиторями Git буде доступна тільки із вказаного діапазону IP-адрес. Підтримується декілька адрес, розділених комами. <br>Наприклад: <code>192.168.0.0/24</code>.%{read_more_link}."
- - "Este grupo, incluyendo todos los subgrupos, proyectos y repositorios git, sólo será accesible desde el rango de direcciones IP especificado. Se admiten múltiples direcciones delimitadas con comas. <br>Ejemplo: <code>192.168.0.0/24,192.168.1.0/24</code>. %{read_more_link}."
"This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\\\"_blank\\\" rel=\\\"noopener noreferrer\\\">enable billing <i class=\\\"fa fa-external-link\\\" aria-hidden=\\\"true\\\"></i></a> and try again.":
plural_id:
translations:
@@ -301,41 +196,6 @@
- "%{issuableDisplayName}(을)를 잠금해제 하시겠습니까? <strong>모두가</strong> 코멘트 할 수 있게 됩니다."
- "Déverrouiller %{issuableDisplayName} ? <strong>Tout le monde</strong> sera en mesure de commenter."
- "Desbloquear este %{issuableDisplayName}? <strong>Todos</strong> podrán comentar."
-"UserOnboardingTour|Issues are great for communicating and keeping track of progress in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\\\"badge color-label accept-mr-label\\\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks when the list is filtered by a label.":
- plural_id:
- translations:
- - "課題は、GitLab 上でコミュニケーションを取り、進行状況を追跡するのに最適な機能です。これらはすべて %{emphasisStart}%{projectName}%{emphasisEnd} の未解決の課題です。%{lineBreak}%{lineBreak} <span class=\\\"badge color-label accept-mr-label\\\">マージリクエストの承認</span> というラベルの付いた課題に作業を貢献することで、GitLab 本体の改善にご協力いただけます。%{lineBreak}%{lineBreak}このリストはラベル、マイルストーン、担当者、作成者などで絞り込むことができます。リストがラベルで絞り込まれたときの様子を紹介します。"
- - "Обсуждения в GitLab отлично подходят для коммуникации и отслеживания прогресса. Перед вами все обсуждения, открытые в %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}Вы можете помочь нам улучшить GitLab, приняв участие в работе над обсуждениями с меткой <span class=\\\"badge color-label accept-mr-label\\\">Accepting merge requests</span> (принимаются запросы на слияние).%{lineBreak}%{lineBreak}Этот список может быть отфильтрован по меткам, этапам, испонителям, авторам... Мы покажем вам, как выглядит отфильтрованный по метке список."
- - "议题非常适合在GitLab中进行沟通和跟踪进展。这些是在%{emphasisStart}%{projectName}%{emphasisEnd}中开启的全部议题。%{lineBreak}%{lineBreak}您可以通过为标记为<span class=\\\"badge color-label accept-mr-label\\\">Accepting merge requests</span>的议题作出贡献来帮助我们改进GitLab。%{lineBreak}%{lineBreak}此列表可以按标签,里程碑,受让人,作者进行过滤......我们将向您展示列表按标签过滤时的样子。"
- - "Задачі добре підходять для комунікації та відстеження прогресу в GitLab. Ось задачі, які відкриті в %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}Ви можете допомогти нам покращити GitLab шляхом внесків у задачі, що відмічені <span class=\\\"badge color-label accept-mr-label\\\">Приймаються запити на злиття</span>.%{lineBreak}%{lineBreak}Цей список може бути відвільтрований за мітками, етапами, виконавцями, авторами... Ми продоемонструємо як виглядає список, відфільтрований за міткою."
-"You can invite a new member to <strong>%{project_name}</strong> or invite another group.":
- plural_id:
- translations:
- - "新しいメンバーを<strong>%{project_name} </strong>に招待するか、別のグループを招待することができます。"
- - "Podes convidar um novo para <strong>%{project_name}</strong> ou convidar outro grupo."
- - "邀请新成员或另一个群组加入<strong>%{project_name}</strong>。"
- - "Puede invitar a un nuevo miembro a <strong>%{project_name}</strong> o invitar a otro grupo."
- - "<strong>%{project_name}</strong> projesine yeni bir üye davet edebilir veya başka bir grubu davet edebilirsiniz."
- - "Вы можете пригласить нового участника в <strong>%{project_name}</strong> или пригласить другую группу."
- - "Ви можете запросити нового учасника до <strong>%{project_name}</strong> або запросити іншу групу."
-"You can invite a new member to <strong>%{project_name}</strong>.":
- plural_id:
- translations:
- - "新しいメンバーを<strong>%{project_name} </strong>に招待できます。"
- - "Podes convidar um novo membro para <strong>%{project_name}</strong>."
- - "邀请新成员加入<strong>%{project_name}</strong>。"
- - "Puedes invitar a un nuevo miembro a <strong>%{project_name}</strong>."
- - "<strong>%{project_name}</strong> projesine yeni bir üye davet edebilirsiniz."
- - "Вы можете пригласить нового участника в <strong>%{project_name}</strong>."
- - "Ви можете запросити нового учасника до <strong>%{project_name}</strong>."
-"You can invite another group to <strong>%{project_name}</strong>.":
- plural_id:
- translations:
- - "他のグループを<strong>%{project_name} </strong>に招待できます。"
- - "Podes convidar outro grupo para <strong>%{project_name}</strong>."
- - "您可以邀请另一个群组加入<strong>%{project_name}</strong>。"
- - "Ви можете запросити нову групу до <strong>%{project_name}</strong>."
- - "Puedes invitar a otro grupo a <strong>%{project_name}</strong>."
"confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue.":
plural_id:
translations:
@@ -357,38 +217,6 @@
- "Vous êtes sur le point de d’activer la confidentialité. Cela signifie que seuls les membres de l’équipe avec <strong>au moins un accès en tant que rapporteur</strong> seront en mesure de voir et de laisser des commentaires sur le ticket."
- "Va a activar la confidencialidad. Esto significa que solo los miembros del equipo con como mínimo,<strong>acceso como Reporter</strong> podrán ver y dejar comentarios sobre la incidencia."
- "あなたは非公開設定をオンにしようとしています。これは、最低でも<strong>報告権限</strong>を持ったチームメンバーのみが課題を表示したりコメントを残したりすることができるようになるということです。"
-"Configure the Jira integration first on your project's %{strong_start} Settings > Integrations > Jira%{strong_end} page.":
- plural_id:
- translations:
-"ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{accepted_count}</strong> accepted.":
- plural_id:
- translations:
-"Only users with an email address in this domain can be added to the group.<br>Example: <code>gitlab.com</code>. Some common domains are not allowed. %{read_more_link}.":
- plural_id:
- translations:
-"PackageRegistry|You are about to delete <b>%{packageName}</b>, this operation is irreversible, are you sure?":
- plural_id:
- translations:
-"Pagination|Last »":
- plural_id:
- translations:
- - "Último >>"
-"Pagination|« First":
- plural_id:
- translations:
- - "<< Primeiro"
-"Example: <code>192.168.0.0/24</code>. %{read_more_link}.":
- plural_id:
- translations:
-"Note that PostgreSQL %{pg_version_upcoming} will become the minimum required version in GitLab %{gl_version_upcoming} (%{gl_version_upcoming_date}). Please consider upgrading your environment to a supported PostgreSQL version soon, see <a href=\\\"%{pg_version_upcoming_url}\\\">the related epic</a> for details.":
- plural_id:
- translations:
-"Authorize <strong>%{user}</strong> to use your account?":
- plural_id:
- translations:
-"DeployFreeze|Specify times when deployments are not allowed for an environment. The <code>gitlab-ci.yml</code> file must be updated to make deployment jobs aware of the %{freeze_period_link_start}freeze period%{freeze_period_link_end}.":
- plural_id:
- translations:
#
# Strings below are fixed in the source code but the translations are still present in CrowdIn so the
@@ -396,425 +224,6 @@
# https://gitlab.com/gitlab-org/gitlab/-/issues/226008
#
-"Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone.":
- plural_id:
- translations:
- - "點擊左上角的<strong>提升</strong>按鈕,將提升至群組里程碑。"
- - "Clique no botão <strong>Promover</strong> no canto superior direito para promover a um marco de grupo."
- - "右上の<strong>昇格</strong>ボタンをクリックしてグループマイルストーンへ昇格"
- - "Clica no botão <strong>Promover</strong> no canto superior direito para promovê-lo para um objetivo de grupo."
- - "点击右上角的 <strong>升级</strong> 按钮以升级到到群组里程碑。"
- - "Натисніть кнопку <strong>Перенести</strong> у правому верхному куті щоб перенести етап на рівень групи."
- - "Klicke auf die Schaltfläche <strong>Hochstufen</strong> in der oberen rechten Ecke, um einen ihn zu einem Gruppenmeilenstein hochzustufen."
- - "오른쪽 상단 모서리의 <strong>승격</strong> 단추를 눌러 그룹 마일스톤으로 보낼 수 있습니다."
- - "Cliquez sur le bouton <strong>Promouvoir</strong> en haut à droite pour le promouvoir en tant que jalon de groupe."
- - "Haga clic sobre el botón <strong>Promocionar</strong> , situado en la esquina superior derecha para promocionarlo a un hito de grupo."
-"Click any <strong>project name</strong> in the project list below to navigate to the project milestone.":
- plural_id:
- translations:
- - "在專案列表點擊任何<strong>專案名稱</strong>,將轉跳到專案的里程碑。"
- - "Clique em qualquer <strong>nome de projeto</strong> na lista a seguir para navegar para o marco do projeto."
- - "プロジェクトリストで<strong>プロジェクト名</strong>をクリックすると、プロジェクトのマイルストーンに移動します。"
- - "Clica em qualquer <strong>nome de projeto</strong> na lista a seguir para navegar para o objetivo do projeto."
- - "Выберите из списка любой <strong>проект</strong>, чтобы перейти к этапу проекта."
- - "单击下面项目列表中的任何 <strong>项目名称</strong> 跳转到项目里程碑。"
- - "Клікніть по будь-якому <strong>імені проекту</strong> зі списку нижче для того, щоб перейти до етапу проекту."
- - "Klicke auf einen beliebigen <strong>-Projektnamen</strong> in der folgenden Projektliste, um zum Projektmeilenstein zu navigieren."
- - "아래 프로젝트 목록에서 <strong>프로젝트 이름</strong>을 눌러 프로젝트 마일스톤을 봅니다."
- - "Cliquez sur n’importe quel <strong>nom de projet</strong> dans la liste des projets ci‐dessous pour naviguer jusqu’au jalon du projet."
- - "Haga clic en cualquier <strong>nombre de proyecto</strong> en la lista de proyectos que se muestra a continuación para navegar hasta el hito de proyecto correspondiente."
-"<namespace / project>":
- plural_id:
- translations:
- - "<namespace / project>"
- - "<простір імен / проєкт>"
-"<no name set>":
- translations:
- - "<nenhum nome definido>"
- - "<no name set>"
- - "<未設定名稱>"
- - "<nenhum nome definido>"
- - "<no name set>"
- - "<未设置名称>"
- - "<ім’я не задане>"
- - "<no name set>"
- - "<sense nom establert>"
- - "<no tiene el nombre establecido>"
- - "<isim belirlenmemiş>"
-"<no scopes selected>":
- translations:
- - "<nenhum escopo selecionado>"
- - "<スコープが選択されていません>"
- - "<未選擇範圍>"
- - "<nenhum escopo selecionado>"
- - "<no scopes selected>"
- - "<未选择范围>"
- - "<область дії не вибрано>"
- - "<keine Bereiche ausgewählt>"
- - "<ningún alcance seleccionado>"
- - "<hiçbir kapsam seçilmedi>"
-"AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings > General.":
- translations:
- - "Elasticsearch、PlantUML、Slackアプリケーション、サードパーティのオファー、Snowplow、Amazon EKS は 設定 > 全般 に移動しました。"
- - "Elasticsearch, PlantUML, приложение Slack, предложения от третьих лиц, Snowplow, Amazon EKS были перемещены в Настройки > Общие"
- - "Elasticsearch, PlantUML, застосунок Slack, пропозиції від третіх осіб, Snowplow, Amazon EKS були переміщені до Налаштувань > Загальне."
-"cannot contain HTML/XML tags, including any word between angle brackets (<,>).":
- translations:
-"Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with <code>K8S_SECRET_</code>. You can set variables to be:":
- plural_id:
- translations:
-"<code>Protected</code> to expose them to protected branches or tags only.":
- plural_id:
- translations:
-"<code>Masked</code> to prevent the values from being displayed in job logs (must match certain regexp requirements).":
- plural_id:
- translations:
-"Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>.":
- plural_id:
- translations:
- - "各ジョブのアーティファクトのデフォルトの有効期限を設定します。 無制限の場合は0。 デフォルトの単位は秒ですが、代わりのものを定義できます。 例:<code>4分2秒</code>、<code>2時間42分</code>"
- - "设置每个作业的产物的默认到期时间。 0 表示无限制。默认以秒为单位,但您可以定义替代方案。例如:<code>4 mins 2 sec</code>, <code>2h42min</code>.。"
- - "Задайте стандартний час зберігання для артефактів кожного із завдань. 0 означає необмежений. Стандартною одиницею вимірювання є секунда (sec), але ви можете вибрати й іншу. Наприклад: <code>4 mins 2 sec</code>, <code>2h42min</code>."
- - "Establezca el tiempo de caducidad predeterminado para los artefactos generados en cada trabajo. 0 para una caducidad ilimitada. La unidad predeterminada son segundos, pero puede definir una alternativa. Por ejemplo: <code>4 minutos 2 segundos</code>, <code>2h42min</code>."
-"Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>.":
- plural_id:
- translations:
- - "ジョブが期限切れとなる期間を設定します。設定された時間を経過するとジョブはアーカイブされ、再試行できなくなります。ジョブの期限を無期限にする場合、空のままにしてください。これらは1日以上で指定しなければなりません。例えば、<code>15 days</code>、 <code>1 month</code>、 <code>2 years</code> など。"
- - "设置作业的过期时间。一旦逾期,作业将被归档并且不能重试。留空则永不过期。必须设置为 1 天以上,例如: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>。"
- - "Встановіть тривалість, протягом якої завдання будуть вважатися застарілими. Як тільки пройде цей час, вони будуть заархівовані і більше не зможуть бути повтореними. Залиште порожнім, щоб завдання ніколи не застарівали. Має бути не менше 1 дня, наприклад: <code>15 днів</code>, <code>1 місяць</code>, <code>2 роки</code>."
- - "Establezca la duración durante la cual los trabajos se considerarán antiguos y vencidos. Una vez pasado ese tiempo, los trabajos se archivarán y ya no se podrá volver a reintentar su ejecución. Establezca este campo como nulo para que los trabajos no caduquen. Este valor, no debe ser inferior a 1 día, por ejemplo: <code>15 días</code>, <code>1 mes</code>, <code>2 años</code>."
-"<strong>%{group_name}</strong> group members":
- plural_id:
- translations:
- - "<strong>%{group_name}</strong> 群組成員"
- - "Membros do grupo <strong>%{group_name}</strong>"
- - "<strong>%{group_name}</strong> グループのメンバー"
- - "<strong>%{group_name}</strong> 群組的成員"
- - "Membros do grupo <strong>%{group_name}</strong>"
- - "<strong>%{group_name}</strong> участников группы"
- - "<strong>%{group_name}</strong> 群组成员"
- - "<strong>%{group_name}</strong> користувачі групи"
- - "<strong>%{group_name}</strong> Gruppenmitglieder"
- - "<strong>%{group_name}</strong> 그룹 멤버"
- - "Membres du groupe <strong>%{group_name}</strong>"
- - "miembros del grupo <strong>%{group_name}</strong>"
- - "<strong>%{group_name}</strong> grup üyeleri"
-"Read more about project permissions <strong>%{link_to_help}</strong>":
- plural_id:
- translations:
- - "Saiba mais sobre as permissões do projeto <strong>%{link_to_help}</strong>"
- - "プロジェクトの権限については <strong>%{link_to_help}</strong> を参照"
- - "了解有关项目权限的更多信息 <strong>%{link_to_help}</strong>"
- - "Дізнайтеся більше про права доступу в проекті <strong>%{link_to_help}</strong>"
- - "Lies mehr über Projektberechtigungen <strong>%{link_to_help}</strong>"
- - "Pour en savoir plus sur les autorisations de projet : <strong>%{link_to_help}</strong>"
- - "Lea más sobre los permisos de este proyecto <strong>%{link_to_help}</strong>"
-"Your license will be included in your GitLab backup and will survive upgrades, so in normal usage you should never need to re-upload your <code>.gitlab-license</code> file.":
- plural_id:
- translations:
-"ContributionAnalytics|<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong> commits by <strong>%{people}</strong> contributors.":
- plural_id:
- translations:
- - "<strong>%{pushes}</strong>回のプッシュ、<strong>%{commits}</strong>回以上のコミットが貢献者<strong>%{people}</strong>によって行われました。"
- - "<strong>%{pushes}</strong>次推送,含来自<strong>%{people}</strong>位贡献者的<strong>%{commits}</strong>次以上提交。"
- - "<strong>%{pushes}</strong> отправок — больше чем <strong>%{commits}</strong> коммитов от <strong>%{people}</strong> участников."
-"ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{closed_count}</strong> closed.":
- plural_id:
- translations:
- - "<strong>%{created_count}</strong> 件を作成、<strong>%{closed_count}</strong>件をクローズしました。"
- - "已创建<strong>%{created_count}</strong> 个,已关闭<strong>%{closed_count}</strong>个。"
- - "<strong>%{created_count}</strong> створено, <strong>%{closed_count}</strong> закрито."
- - "<strong>%{created_count}</strong> creado, <strong>%{closed_count}</strong> cerrado."
- - "<strong>%{created_count}</strong> oluşturuldu, <strong>%{closed_count}</strong> kapatıldı."
-"ContributionAnalytics|<strong>%{created_count}</strong> created, <strong>%{merged_count}</strong> merged.":
- plural_id:
- translations:
-"<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> will add \\\"By <a href=\\\"#\\\">@johnsmith</a>\\\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\\\"#\\\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com.":
- plural_id:
- translations:
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> 將會在所有原本由 johnsmith@example.com 建立的議題和留言中加上「來自 <a href=\\\"#\\\">@johnsmith</a>」並將原本分配給 johnsmith@example.com 的所有議題設定 <a href=\\\"#\\\">@johnsmith</a> 為被指派人。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> adicionará \\\"Por <a href=\\\"#\\\">@johnsmith</a>\\\" a todas as issues e comentários originalmente criados por johnsmith@example.com e definirá <a href=\\\"#\\\">@johnsmith</a> como o responsável em todas as issues originalmente atribuídas a johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> は johnsmith@example.com による全ての課題とコメントに \\\"By <a href=\\\"#\\\">@johnsmith</a>\\\" を追加します。また、 <a href=\\\"#\\\">@johnsmith</a> を元々 johnsmith@example.com に割り当てられていた全ての課題の担当者として設定します。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> aggiungerà \\\"Da <a href=\\\"#\\\">@johnsmith</a>\\\"per tutti gli issue e i commenti creati originariamente da johnsmith@example.com, e imposterà <a href=\\\"#\\\">@johnsmith</a> come assegnatario su tutti gli issue originariamente assegnati a johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> 將會把「由 <a href=\\\"#\\\">@johnsmith</a>」加入到原本由 johnsmith@example.com 建立的所有議題和留言中,並將 <a href=\\\"#\\\">@johnsmith</a> 設為原本分配給johnsmith@example.com 所有議題的受讓人。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> adicionará \\\"Por <a href=\\\"#\\\">@johnsmith</a>\\\" a todos os problemas e comentários, originalmente, criados por johnsmith@example.com e definirá <a href=\\\"#\\\">@johnsmith</a> como o responsável de todos os problemas, originalmente, atribuídos a johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> 将会把“由<a href=\\\"#\\\">@johnsmith</a>”添加到原本由johnsmith@example.com创建的所有议题和评论中,并将 <a href=\\\"#\\\">@johnsmith</a> 设为原本分配给johnsmith@example.com所有问题的受让人。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> додасть \\\"<a href=\\\"#\\\">@johnsmith</a>\\\" до всіх задач та коментарів, що були створені johnsmith@example.com, а також призначить на <a href=\\\"#\\\">@johnsmith</a> усі задачі, які були призначені на на johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> fügt allen von johnsmith@example.com erstellten Tickets und Kommentaren \\\"Von <a href=\\\"#\\\">@johnsmith</a>\\\" hinzu und setzt <a href=\\\"#\\\">@johnsmith</a> als Zuständigen für alle Tickets die ursprünglich johnsmith@example.com zugewiesen waren."
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> 은 원래 johnsmith@example.com이 생성한 모든 이슈와 의견에 \\\"By <a href=\\\"#\\\">@johnsmith</a>\\\"을 추가하고 원래 johnsmith@example.com에 할당된 모든 이슈에 양수인으로 <a href=\\\"#\\\">@ johnsmith</a> 로 설정합니다."
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> ajoutera « Par <a href=\\\"#\\\">@johnsmith</a> » à tous les tickets et commentaires créés à l’origine par johnsmith@example.com, et tous les tickets initialement assignés à johnsmith@example.com seront assignés à <a href=\\\"#\\\">@johnsmith</a>."
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> añadirá \\\"Por <a href=\\\"#\\\">@johnsmith</a>\\\" a todas las incidencias y comentarios creados por johnsmith@example.com, y colocará a <a href=\\\"#\\\">@johnsmith</a> como la persona asiganda a todas las incidencias inicialmente asignadas a johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"@johnsmith\\\"</code> добавил \\\"<a href=\\\"#\\\">@johnsmith</a>\\\" ко всем обсуждениям и комментариям, изначально созданным johnsmith@example.com, и установил <a href=\\\"#\\\">@johnsmith</a> в качестве ответственного по всем обсуждениям, на которые изначально был назначен johnsmith@example.com."
-"<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> will add \\\"By John Smith\\\" to all issues and comments originally created by johnsmith@example.com.":
- plural_id:
- translations:
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> 將會在所有原本由 johnsmith@example.com 建立的跟進事宣和留言中加上「來自 John Smith」。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> adicionará \\\"Por John Smith\\\" a todas as issues e comentários originalmente criados por johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> は、johnsmith@example.com によって元々作成された全ての課題とコメントに \\\"By John Smith\\\" を追加します。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> aggiungerà \\\"Da John Smith\\\" per tutti i problemi e i commenti creati originariamente da johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> 將會把「由 John Smith」加入到原本由 johnsmith@example.com 建立的所有議題和留言中。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> adicionará \\\"Por John Smith\\\" a todos os problemas e comentários, originalmente, criados por johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> добавит \\\"От John Smith\\\" ко всем обсуждениям и комментариям, созданным johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> 将会把\\\"由John Smith\\\"添加到原本由johnsmith@example.com创建的所有议题和评论中。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> додасть \\\"John Smith\\\" до усіх задач та коментарів, які були створені johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> fügt \\\"Von John Smith\\\" zu allen Tickets und Kommentaren hinzu, die ursprünglich von johnsmith@example.com erstellt wurden."
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> 은 johnsmith@example.com이 처음 생성한 모든 이슈와 주석에 \\\"By John Smith\\\"를 추가합니다."
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code> ajoutera « Par John Smith » à tous les tickets et commentaires créés à l’origine par johnsmith@example.com."
- - "<code>\\\"johnsmith@example.com\\\": \\\"John Smith\\\"</code>añadirá \\\"por John Smith\\\" a todas las incidencias y comentarios creados originalmente por johnsmith@example.com."
-"<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> will add \\\"By johnsm...@example.com\\\" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user's privacy.":
- plural_id:
- translations:
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> 將會在所有原本由 johnsmith@example.com 建立的議題和留言中加上「來自 johnsm...@example.com」。電子信箱位址或使用者名稱將受遮蔽,保障用家私隱。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> adicionará \\\"Por johnsm...@example.com\\\" a todas as issues e comentários originalmente criados por johnsmith@example.com. O endereço de e-mail ou nome de usuário é mascarado para garantir a privacidade do usuário."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> は、johnsmith@example.com が作成した全ての課題とコメントに \\\"By johnsm...@example.com\\\" を追加します。このメールアドレスやユーザー名を隠してユーザーのプライバシーを保護されます。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> Aggiungerà \\\"Da johnsm...@example.com\\\"per tutti i problemi e i commenti creati originariamente da johnsmith@example.com. L'indirizzo email o il nome utente sono mascherati per garantire la privacy dell'utente."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> 將會把「由 johnsm...@example.com」加入到原本由 johnsmith@example.com 建立的所有議題和留言中。為保護使用者的隱私,電子郵件地址或使用者名稱會被遮住。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> adicionará \\\"Por johnsm...@example.com\\\" a todos os problemas e comentários, originalmente, criados por johnsmith@example.com. O endereço de email ou nome de utilizador é ocultado para garantir a privacidade do utilizador."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> добавит \\\"От johnsm...@example.com\\\" ко всем обсуждениям и комментариям, созданным пользователем johnsmith@example.com. Почтовый адрес и имя пользователя скрываются для обеспечения конфиденциальности."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> 将会把\\\"由johnsm...@example.com\\\"添加到原本由johnsmith@example.com创建的所有议题和评论中。 为保护用户的隐私,电子邮件地址或用户名将被屏蔽。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> додасть \\\"johnsm...@example.com\\\" до усіх задач та коментарів, які були створені johnsmith@example.com. Ім’я користувача та його електронна адреса замасковані для забезпечення конфіденційності."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> fügt \\\"Von johnsm...@example.com\\\" zu allen Tickets und Kommentaren hinzu, die ursprünglich von johnsmith@example.com erstellt wurden. Die E-Mail-Adresse oder der Benutzername ist maskiert, um die Privatsphäre des/der Benutzers/Benutzerin zu gewährleisten."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm... @ example.com\\\"</code> 은 원래 johnsmith@example.com이 생성한 모든 이슈와 주석에 \\\"By johnsm... @ example.com\\\"을 추가합니다. 전자 메일 주소 또는 사용자 이름은 사용자의 개인 정보를 보호하기 위해 마스킹 처리됩니다."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> ajoutera « Par johnsm...@example.com » à tous les tickets et commentaires créés à l’origine par johnsmith@example.com. L’adresse de courriel ou le nom d’utilisateur est masqué pour garantir la confidentialité de l’utilisateur."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsm...@example.com\\\"</code> añadirá \\\"Por johnsm...@example.com\\\" a todas las incidencias y comentarios originalmente creados por johnsmith@example.com. El correo electrónico o nombre de usuario está oculto para asegurar la privacidad del usuario."
-"<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> will add \\\"By <a href=\\\"#\\\">johnsmith@example.com</a>\\\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address.":
- plural_id:
- translations:
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> adicionará \\\"Por <a href=\\\"#\\\">johnsmith@example.com</a>\\\" a todas as issues e comentários originalmente criados por johnsmith@example.com. Por padrão, o endereço de e-mail ou nome de usuário é mascarado para garantir a privacidade do usuário. Use esta opção se você quiser mostrar o endereço de e-mail completo."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> は、johnsmith@example.com が作成した全ての課題とコメントに \\\"By <a href=\\\"#\\\">johnsmith@example.com</a>\\\" を追加します。デフォルトで、メールアドレスやユーザー名を隠してユーザーのプライバシーを保護されます。メールアドレスを全て表示したい場合、この方法を指定してください。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> 將會把「由 <a href=\\\"#\\\">@johnsmith</a>」加入到原本由 johnsmith@example.com 建立的所有議題和留言中。為保護使用者的隱私,電子郵件地址或使用者名稱預設會被遮住。如需顯示完整郵件地址,可使用此選項。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> adicionará \\\"Por <a href=\\\"#\\\">johnsmith@example.com</a>\\\" a todos os problemas e comentários, originalmente, criados por johnsmith@example.com. Por padrão, o endereço de email ou nome de utilizador é ocultado para garantir a privacidade do utilizador. Use esta opção se quiser mostrar o endereço de email completo."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> добавит \\\"От <a href=\\\"#\\\">johnsmith@example.com</a>\\\" ко всем обсуждениям и комментариям, созданным johnsmith@example.com. По умолчанию адрес почты и имя пользователя скрываются для обеспечения конфиденциальности. Используйте эту опцию, если хотите показывать полный адрес электронной почты."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> 将会把“由<a href=\\\"#\\\">@johnsmith</a>”添加到原本由johnsmith@example.com创建的所有议题和评论中。 为保护用户的隐私,电子邮件地址或用户名默认将被屏蔽。如需显示完整邮件地址,可使用此选项。"
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> додасть \\\"<a href=\\\"#\\\">johnsmith@example.com</a>\\\" до всіх задач та коментарів, які були створені johnsmith@example.com. За замовчуванням ім’я користувача та його електронна адреса заблоковані для забезпечення конфіденційності. Використовуйте цю опцію, якщо ви хочете показувати електронну адресу повністю."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> fügt \\\"Von <a href=\\\"#\\\">johnsmith@example.com</a>\\\" zu allen Tickets und Kommentaren hinzu, die ursprünglich von johnsmith@example.com erstellt wurden. Standardmäßig wird die E-Mail-Adresse maskiert, um den Datenschutz des Nutzers zu gewährleisten. Nutze diese Option, wenn du die volle E-Mail-Adresse anzeigen willst."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> 은 원래 johnsmith@example.com이 생성한 모든 이슈와 주석에 \\\"By <a href=\\\"#\\\">johnsmith@example.com</a>\\\"을 추가합니다. 기본적으로 이메일 주소 또는 사용자 이름은 가려져 있어서 사용자의 개인정보를 보호합니다. 전체 전자 메일 주소를 표시하려면 이 옵션을 사용하십시오."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> ajoutera « Par <a href=\\\"#\\\">johnsmith@example.com</a> » à tous les tickets et commentaires créés à l’origine par johnsmith@example.com. Par défaut, l’adresse de courriel ou le nom d’utilisateur est masqué pour garantir la confidentialité de l’utilisateur. Utilisez cette option si vous souhaitez afficher l’adresse de courriel complète."
- - "<code>\\\"johnsmith@example.com\\\": \\\"johnsmith@example.com\\\"</code> añadirá \\\"Por <a href=\\\"#\\\">johnsmith@example.com</a>\\\" a todas las incidencias y comentarios originalmente creados por johnsmith@example.com. Por defecto, el correo electrónico o el nombre de usuario está oculto para asegurar la privacidad del usuario. Utilice esta opción si desea mostrar la dirección de correo electrónico completa."
-"Choose <strong>Create archive</strong> and wait for archiving to complete.":
- plural_id:
- translations:
- - "Escolha <strong>Criar arquivo</strong> e aguarde até que o arquivamento seja concluído."
- - "<strong>アーカイブの作成</strong>を選択し、アーカイブ作成の完了をお待ちください。"
- - "Escolhe <strong>Criar arquivo</strong> e aguarda até que o arquivamento seja concluído."
- - "选择<strong>创建归档</strong> 并等待归档过程完成。"
- - "Оберіть <strong>Створити архів</strong> і чекайте, поки архівування буде завершено."
- - "Wähle <strong>Archiv erstellen</strong> und warte, bis die Archivierung abgeschlossen ist."
- - "<strong>보관 파일 생성</strong>을 선택하고 보관이 완료 될 때까지 기다립니다."
- - "Sélectionnez <strong>Créer une archive</strong> et attendez que l’archivage soit terminé."
- - "Elija <strong>Crear archivo</strong> y espere a que se complete el archivo."
-"Choose <strong>Next</strong> at the bottom of the page.":
- plural_id:
- translations:
- - "Escolha <strong>Próximo</strong> na parte inferior da página."
- - "このページの下部にある<strong>次へ</strong>を選択してください。"
- - "Escolhe <strong>Próximo</strong> na parte inferior da página."
- - "选择页面底部的<strong>下一步</strong>。"
- - "Оберіть <strong>Далі</strong> внизу сторінки."
- - "Wähle <strong>Nächste</strong> unten auf der Seite."
- - "페이지의 아래의 <strong>다음</strong> 선택"
- - "Cliquez sur <strong>Suivant</strong> au bas de la page."
- - "Elija <strong>Siguiente</strong> en la parte inferior de la página."
- - "Sayfanın altındaki <strong>Sonraki</strong> düğmesini seçin."
-"Click the <strong>Download</strong> button and wait for downloading to complete.":
- plural_id:
- translations:
- - "Clique no botão <strong>Baixar</strong> e aguarde a conclusão do download."
- - "<strong>ダウンロード</strong> ボタンをクリックし、ダウンロードの完了をお待ちください。"
- - "Clica no botão <strong>Transferir</strong> e aguarda a finalização do mesmo."
- - "点击 <strong>下载</strong> 按钮,等待下载完成。"
- - "Натисніть кнопку <strong>Завантаження</strong> і зачекайте поки завантаження не завершиться."
- - "Klicke auf den <strong>Download</strong>-Button und warte bis das Herunterladen abgeschlossen ist."
- - "<strong>다운로드</strong> 버튼을 클릭하고 다운로드가 완료 될 때까지 기다려 주세요."
- - "Cliquez sur le bouton <strong>Télécharger</strong> et attendez que le téléchargement soit terminé."
- - "Haga click en el botón <strong>Descargar</strong> y espere a que se complete la descarga."
- - "<strong>İndirme</strong> düğmesini tıklayın ve indirme işleminin tamamlanmasını bekleyin."
-"Click the <strong>Select none</strong> button on the right, since we only need \\\"Google Code Project Hosting\\\".":
- plural_id:
- translations:
- - "Clique no botão à direita <strong>Selecionar nenhum</strong>, uma vez que só precisamos do \\\"Google Code Project Hosting\\\"."
- - "右の<strong>Select none</strong>ボタンをクリックしてください。これは、GitLabに必要なのは\\\"Google Code Project Hosting\\\"だけだからです。"
- - "Clica no botão <strong>Selecionar nenhum</strong> à direita, pois, só precisamos de \\\"Google Code Project Hosting\\\"."
- - "请点击右边的 <strong>无</strong> 按钮,因为我们只需要“Google Code项目托管”。"
- - "Натисніть кнопку <strong>Обрати нічого</strong> справа, оскільки нам потрібен лише \\\"Хостинг проектів Google Code\\\"."
- - "Klicke auf die Schaltfläche <strong>Keine auswählen</strong> auf der rechten Seite, da wir nur \\\"Google Code Project Hosting\\\" benötigen."
- - "오른쪽의 <strong>선택 없음</strong> 버튼을 클릭 하십시오. 우리는 \\\"\\\"Google Code Project Hosting\\\" 만 사용하기 때문에 다른 것은 필요 없습니다. "
- - "Cliquez sur le bouton <strong>Sélectionner aucun</strong> sur la droite, puisque nous n’avons seulement besoin que de « Google Code Project Hosting »."
- - "Haga click en el botón <strong>Seleccionar uno</strong> en la parte derecha, ya que sólo necesitamos \\\"Google Code Project Hosting\\\"."
-"Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file.":
- plural_id:
- translations:
- - "Encontre o arquivo recém-extraído <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code>."
- - "新しく抽出された <code>テイクアウト/ Googleコードプロジェクトホスティング/ GoogleCodeProjectHosting.json</code> ファイルを探します。"
- - "查找新提取的 <code>Takeout/Google Code项目托管/GoogleCodeProjectHosting. json</code> 文件。"
- - "Знайдіть щойно розпакований <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> файл."
- - "Suche die neu extrahierte Datei <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code>."
- - "Cherchez le fichier <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> fraîchement extrait."
- - "Busque el archivo extraído <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code>."
-"Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right.":
- plural_id:
- translations:
- - "Role para baixo até <strong>Google Code Project Hosting</strong> e ative a opção à direita."
- - "<strong>Google Code Project Hosting</strong> にスクロールし、右側のスイッチを有効にします。"
- - "向下滚动到 <strong>Google Code项目托管</strong> 并通过右侧的开关启用。"
- - "Прокрутіть вниз до <strong>Google Code Project Hosting</strong> і увімкніть перемикач праворуч."
- - "Scrolle nach unten zu <strong>Google Code Project Hosting</strong> und aktiviere den Schalter auf der rechten Seite."
- - "Faites défiler jusqu’à <strong>Hébergement de projet Google Code</strong> et activez le commutateur sur la droite."
- - "Desplácese hasta <strong>Google Code Project</strong> y active el selector de la derecha."
-"The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.":
- plural_id:
- translations:
- - "O mapa do usuário é um documento JSON que mapeia os usuários do Google Code que participaram de seus projetos para a maneira como seus endereços de e-mail e nomes de usuários são importados para o GitLab. Você pode mudar isso alterando o valor no lado direito de <code>:</code>. Certifique-se de preservar as aspas duplas adjacentes, outros sinais de pontuação e o endereço de e-mail ou nome de usuário no lado esquerdo."
- - "ユーザーマップは、あなたのプロジェクトに参加した Google コードのユーザーのメールアドレスとユーザー名を GitLab にインポートするときにマッピングする JSON ファイルです。これを変更するには、 <code>:</code> の右側の値を変更します。左側の二重引用符、その他の句読点、メールアドレスまたはユーザー名が保存されます。"
- - "用户映射是一个JSON文档,将参与项目的Google Code用户映射到他们将导入GitLab的电子邮件地址和用户名的方式。您可以通过更改 <code>:</code>右侧的值来更改此值。请务必在左侧保留周围的双引号,其他标点符号以及电子邮件地址或用户名。"
- - "Мапа користувачів — це JSON-документ, який задає як адреси електронної пошти та імена користувачів Google Code, що приймали участь у ваших проектах будуть імпортовані у GitLab. Ви можете змінити його шляхом зміни значень, що стоять справа від <code>:</code>. Не видаляйте лапки та інші знаки пунктуації, а також не змінюйте адреси електронної пошти чи імена користувачів зліва."
- - "Die Benutzerzuordnung ist ein JSON-Dokument das festlegt, wie die E-Mail-Adressen und Benutzernamen der Google Code-Benutzer(innen), die an deinem Projekt teilnehmen, in GitLab importiert werden. Du kannst dies ändern, indem du den Wert auf der rechten Seite von <code>:</code> anpasst. Stelle sicher, dass du umgebenden Anführungszeichen, andere Interpunktion sowie die E-Mail-Adresse oder den Benutzernamen auf der linken Seite erhältst."
- - "La carte des utilisateurs (<code>user map</code>) est un document JSON qui met en correspondance les utilisateurs de Google Code qui ont participé à vos projets en précisant la manière dont leurs adresses de courriel et leurs noms d’utilisateur sont importés dans GitLab. Vous pouvez y apporter des modifications en changeant la valeur à droite du « <code>:</code> ». Assurez‐vous de conserver les guillemets droits doubles (<code>\\\"</code>), les autres signes de ponctuation, ainsi que l’adresse de courriel ou le nom d’utilisateur à gauche du deux‐points."
- - "El mapa del usuarios es un documento JSON que asigna los usuarios de Google Code que participaron en sus proyectos a la forma en que se importarán sus direcciones de correo electrónico y nombres de usuario en GitLab. Puede cambiar esto cambiando el valor en el lado derecho de <code>:</code>. Asegúrese de conservar las comillas dobles circundantes, otros signos de puntuación y la dirección de correo electrónico o nombre de usuario en el lado izquierdo."
- - "Mapa użytkownika to dokument JSON mapujący użytkowników Google Code, którzy uczestniczyli w Twoich projektach, w celu zaimportowania ich adresów e-mail i nazw użytkowników do GitLab. Możesz to zmienić poprzez zmianę wartości po prawej stronie <code>:</code>. Pamiętaj, aby zachować występujące podwójne cudzysłowy, inną interpunkcję i adres e-mail lub nazwę użytkownika po lewej stronie."
-"Upload <code>GoogleCodeProjectHosting.json</code> here:":
- plural_id:
- translations:
- - "Envie o <code>GoogleCodeProjectHosting.json</code> aqui:"
- - "<code>GoogleCodeProjectHosting.json</code> をアップロードします:"
- - "Enviar <code>GoogleCodeProjectHosting.json</code> aqui:"
- - "在这里上传 <code>GoogleCodeProjectHosting.json</code>:"
- - "Надіслати <code>GoogleCodeProjectHosting.json</code> тут:"
- - "Lade <code>GoogleCodeProjectHosting.json</code> hier hoch:"
- - "Téléversez le fichier <code>GoogleCodeProjectHosting.json</code> ici :"
- - "Subir el fichero <code>GoogleCodeProjectHosting.json</code> aquí:"
-"Create and provide your GitHub %{link_start}Personal Access Token%{link_end}. You will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import.":
- plural_id:
- translations:
- - "GitHub の%{link_start}Personal Access Token%{link_end} を作成して、提供してください。<code>repo</code> スコープを選択する必要があります。これによりインポートできる公開または非公開リポジトリの一覧を表示できます。"
- - "请创建并提供您的GitHub%{link_start}个人访问令牌%{link_end}。您需要选择<code>repo</code>范围,这样我们才可以显示可导入的公共和私有仓库的列表。"
- - "Створіть або вкажіть ваш %{link_start}Персональний токен доступу%{link_end} GitHub. Вам необхідно буде вибрати область дії <code>repo</code> для того, щоб ми змогли відобразити список ваших публічних та приватних репозиторіїв, доступних для імпорту."
- - "Cree y proporcione su %{link_start}token de acceso personal %{link_end}de GitHub. Necesitará seleccionar el alcance del <code>repositorio</code>, para que podamos mostrar una lista de sus repositorios públicos y privados que estén disponibles para importar."
-"To connect GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect.":
- plural_id:
- translations:
- - "Para conectar repositórios do GitHub, você pode usar um %{personal_access_token_link}. Ao criar seu Token de Acesso Pessoal, você precisará selecionar o escopo do <code>repositório</code>, então podemos exibir uma lista de seus repositórios públicos e privados que estão disponíveis para conexão."
- - "GitHub リポジトリに接続するために %{personal_access_token_link} を使用できます。個人用アクセストークンを作成するには<code>リポジトリ</code> スコープを選択する必要があります。これにより接続できる公開・非公開リポジトリの一覧を表示することができます。"
- - "可以使用 %{personal_access_token_link} 连接GitHub仓库。当创建个人访问令牌时,需要选择 <code>repo</code> 范围,以显示可供连接的公开和私有的仓库列表。"
- - "Для підключення репозиторіїв з GitHub, ви можете використовувати %{personal_access_token_link}. Коли ви створюватимете ваш персональний токен доступу, вам потрібно буде вибрати область дії <code>repo</code>, щоб ми могли відобразити список ваших публічних та приватних репозиторіїв, доступних для підключення."
- - "Um GitHub-Repositories zu verbinden kannst du einen %{personal_access_token_link} verwenden. Wenn du deinen persönlichen Access-Token erzeugst, musst du den Gültigkeitsbereich <code>repo</code> auswählen, damit wir eine Liste deiner öffentlichen und privaten Repositories anzeigen können, die für die Verbindung verfügbar sind."
- - "GitHub 저장소 연결할때, %{personal_access_token_link}을 사용할 수 있습니다. 개인 엑세스 토큰을 생성할때 <code>repo</code> scope를 선택해야 연결에 사용할 수 있는 공개, 비공개 저장소가 표시되어 연결할 수 있게 됩니다."
- - "Pour connecter des dépôts GitHub, vous pouvez utiliser un %{personal_access_token_link}. Lorsque vous créerez votre jeton d’accès, vous devrez sélectionner la portée <code>dépôt</code>, afin de permettre l’affichage d’une liste de vos dépôts publics et privés disponibles à la connexion."
- - "Para conectar a los repositorios de GitHub, puede utilizar un %{personal_access_token_link}. Cuando cree su token de acceso personal, deberá seleccionar el alcance del <code>repo</code>, para que podamos mostrarle una lista de sus repositorios públicos y privados que están disponibles para conectarse."
-"The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.":
- plural_id:
- translations:
- - "O repositório deve ser acessível por <code>http://</code>, <code>https://</code> ou <code>git://</code>."
- - "リポジトリには、<code>http://</code>、 <code>https://</code> または <code>git://</code>で接続できなければなりません。"
- - "Репозиторий должен быть доступен через протоколы <code>http: //</code>, <code>https: //</code> или <code>git: //</code>."
- - "该仓库必须可通过<code>http://</code>, <code>https://</code> 或 <code>git://</code>进行访问。"
- - "Репозиторій має бути доступним через <code>http://</code>, <code>https://</code> або <code>git://</code>."
- - "Das Repository muss über <code>http://</code>, <code>https://</code> oder <code>git://</code> erreichbar sein."
- - "저장소를 <code>http://</code>, <code>https://</code> 또는 <code>git://</code> 통해 액세스할 수 있어야 합니다."
- - "Le dépôt doit être accessible via <code>http://</code>, <code>https://</code> ou <code>git://</code>."
- - "El repositorio debe ser accesible a través de <code>http://</code>, <code>https://</code> o <code>git://</code>."
-"When using the <code>http://</code> or <code>https://</code> protocols, please provide the exact URL to the repository. HTTP redirects will not be followed.":
- plural_id:
- translations:
- - "<code>http://</code> または <code>https://</code> プロトコルを使用する場合、リポジトリの正確なURLを指定してください。 HTTPリダイレクトは追跡されません。"
- - "Используя <code>http://</code> или <code>https://</code> протокол, пожалуйста указывайте точный адрес репозитория. Переадресация выполняться не будет."
- - "使用<code>http://</code>或<code>https://</code>协议时,请提供仓库的实际地址。不支持HTTP重定向。"
- - "При використанні протоколів <code>http://</code> або <code>https://</code> вказуйте, будь ласка, точну URL-адресу репозиторію. Перенаправлення HTTP не будуть виконуватися."
- - "Al utilizar los protocolos <code>http://</code> o <code>https://</code>, por favor, proporcione la URL exacta del repositorio. No se seguirán las redirecciones HTTP."
-"Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \\\"By <a href=\\\"#\\\">@johnsmith</a>\\\"). It will also associate and/or assign these issues and comments with the selected user.":
- plural_id:
- translations:
- - "A seleção de um usuário do GitLab adicionará um link para o usuário do GitLab nas descrições de issues e comentários (por exemplo, \\\"Por <a href=\\\"#\\\">@johnsmith</a>\\\"). Isto também associará e/ou atribuirá esses issues e comentários ao usuário selecionado."
- - "GitLab ユーザーを選択すると、GitLab ユーザーへのリンクが課題やコメントの説明に追加されます (例:\\\"By <a href=\\\"#\\\">@johnsmith</a>\\\")。また、これらの課題やコメントを選択したユーザーに関連付けや割り当てができます。"
- - "选中GitLab用户将在议题和评论的描述中加入指向该GitLab用户的链接(例如“By <a href=\\\"#\\\">@johnsmith</a>”)。它还将与所选用户关联和/或分配这些议题和评论。"
- - "При виборі користувача Gitlab посилання на нього буде додане до опису задачі та коментарів (напр. \\\"<a href=\\\"#\\\"> @johnsmith</a>\\\"). Також це призведе до асоціації та/або призначення цих задач та коментарів на вибраного користувача."
- - "Wenn du eine(n) GitLab-Benutzer(in) azswählst, wird in der Beschreibung des Tickets und den Kommentaren ein Link zum/zur Benutzer(in) hinzugefügt (z. B. \\\"Von <a href=\\\"#\\\">@johnsmith</a>\\\"). Außerdem wird der/die ausgewählte Benutzer(in) dem Ticket oder Kommentar zugeordnet und/oder es ihm/ihr zugewiesen."
- - "La sélection d’un utilisateur de GitLab va ajouter un lien vers cet utilisateur dans les descriptions des tickets et des commentaires (p. ex., « Par <a href=\\\"#\\\">@johnsmith</a> »). Les tickets et commentaires seront également associés ou assignés à cet utilisateur."
- - "Al seleccionar un usuario de GitLab añadirá un link al usuario en la descripción de las incidencias, así como también, en los comentarios (por ejemplo, \\\"Por <a href=\\\"#\\\">@johnsmith</a>\\\"). Al hacer esto, también asociará y asignará dichas incidencias y comentarios con el usuario seleccionado."
-"The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment.":
- plural_id:
- translations:
- - "CSVエクスポートをバックグラウンドで作成します。終了後に、<strong>%{email}</strong> に添付してメールで送信されます。"
- - "Экспорт в CSV будет произведён в фоновом режиме. По завершении результат будет отправлен на <strong>%{email}</strong> во вложении."
- - "CSV导出将在后台创建。完成后,它将以附件形式发送到<strong>%{email}</strong>。"
- - "Експорт CSV буде створено у фоновому режимі. Після завершення його буде надіслано на <strong>%{email}</strong> у вкладенні."
- - "La exportación CSV se creará en segundo plano. Una vez finalizada, será enviada a <strong>%{email}</strong> como un fichero adjunto al correo electrónico."
-"A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project.":
- plural_id:
- translations:
- - "Um fork é uma cópia de um projeto.<br />Fork de um repositório permite que você faça alterações sem afetar o projeto original."
- - "フォークはプロジェクトのコピーです。<br />リポジトリをフォークすると、元のプロジェクトに影響を与えずに変更することができます。"
- - "分叉是專案的複本。<br />分叉版本庫讓您在不影響原始專案的情況下,進行變更。"
- - "Um fork é uma cópia de um projeto.<br />Bifurcação de um repositório permite que faças alterações sem afetar o projeto original."
- - "Ответвление - это копия проекта.<br />Ответвление репозитория позволяет вносить изменения, не влияя на исходный проект."
- - "派生是项目的副本。<br />仓库的派生允许您在不影响原始项目的情况下进行更改。"
- - "Форк - це копія проєкту.<br />Форк репозиторію дозволяє вносити зміни без впливу на оригінальний проєкт."
- - "Ein Fork ist eine Kopie eines Projekts.<br />Wenn du ein Repository forkst, kannst du, ohne Auswirkungen auf das ursprüngliche Projekt, Änderungen vornehmen."
- - "포크는 프로젝트의 사본입니다.<br />저장소를 포크하면 원래 프로젝트에 영향을주지 않고 변경할 수 있습니다."
- - "Un fork es una copia de un proyecto.<br />Realizar un fork de un repositorio le permite realizar cambios sin afectar al proyecto original."
-"Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision.":
- plural_id:
- translations:
- - "Mudanças serão mostradas se revisão de <b>origem</b> tiver sofrido merge na revisão <b>alvo</b>."
- - "<b>source</b>リビジョンが<b>target</b>リビジョン内に取り込まれているような変更として表示されます"
- - "As alterações são mostradas como se a revisão de <b>origem</b> estivesse a ser mesclada na revisão de <b>destino</b>."
- - "Показаны изменения как будто произошло слияние ревизии кода <b>источника</b> с <b>целевой</b> ревизией кода."
- - "差异显示方式依<b>源</b>版本合并到<b>目标</b>版本的形式。"
- - "Зміни відображаються так, ніби <b>редакція-джерело</b> була злита в <b>цільову редакцію</b>."
- - "Änderungen werden angezeigt, als ob die <b>Quell</b>-Revision in die <b>Ziel</b>-Revision gemerged wurde."
- - "변경 사항은 <b>source</b> 리비전이 <b>target</b> 리비전에 머지된 것처럼 표시됩니다."
- - "Les modifications sont affichées comme si la révision <b>source</b> était fusionnée dans la révision<b>cible</b>."
- - "Los cambios se muestran como si la revisión del <b>origen</b> se ha fusionado con la revisión del <b>objetivo</b>."
-"From <code>%{source_title}</code> into":
- plural_id:
- translations:
- - "<code>%{source_title}</code> から"
- - "Від <code>%{source_title}</code> до"
-"Choose between <code>clone</code> or <code>fetch</code> to get the recent application code":
- plural_id:
- translations:
- - "Escolha entre <code>clone</code> ou <code>fetch</code> para obter o código do aplicativo recente"
- - "<code>clone</code> または <code>fetch</code> を選択して最新のアプリケーションコードを取得してください"
- - "Escolhe entre <code>clone</code> ou <code>fetch</code> para obter o código da aplicação recente"
- - "选择 <code>克隆</code> 或 <code>拉取</code> 以获取最近的应用程序代码"
- - "Оберіть між <code>clone</code> та<code>fetch</code> щоб отримати найновіший код програми"
- - "Wähle zwischen <code>clone</code> oder <code>fetch</code>, um den aktuellen Anwendungscode zu erhalten"
- - "최근 응용 프로그램 코드를 얻으려면 <code>클론</code> 또는 <code>fetch</code>를 선택하십시오."
- - "Choisissez entre <code>clone</code> ou <code>fetch</code> pour obtenir les dernières modifications du code de l’application"
- - "Elija entre <code>clone</code> o <code>fetch</code> para obtener el código de la aplicación más reciente"
- - "Wybierz pomiędzy <code>klonem</code> lub <code>pobierz</code> aby uzyskać najnowszy kod aplikacji"
-"The path to the CI configuration file. Defaults to <code>.gitlab-ci.yml</code>":
- plural_id:
- translations:
- - "CI 設定ファイルへのパス。デフォルトは <code>.gitlab-ci.yml</code>"
- - "Путь к файлу конфигурации CI. По умолчанию это <code>.gitlab-ci.yml</code>"
- - "CI配置文件的路径。默认为<code>.gitlab-ci.yml</code>"
- - "Шлях до кофігураційного файлу CI. За замовчуванням <code>.gitlab-ci.yml</code>"
- - "La ruta al archivo de configuración CI. Por defecto <code>.gitlab-ci.yml</code>"
-"Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here.":
- plural_id:
- translations:
- - "デプロイステージの環境を <code>.gitlab-ci.yml</code> で定義して、デプロイを追跡します。"
- - "Обозначьте окружения на этапе(ах) развёртывания в <code>.gitlab-ci.yml</code> для отслеживания развёртывания здесь."
- - "在<code>.githab-ci.yml</code>的 deploy 阶段中定义 environment 来跟踪部署。"
- - "Визначте середовища на стадії розгортання у <code>.gitlab-ci.yml</code> щоб відстежувати розгортання тут."
- - "Defina entornos en las distintas etapas de despliegue en el fichero <code>.gitlab-ci.yml</code> para llevar un registro de las implementaciones desde aquí."
-"Groups with access to <strong>%{project_name}</strong>":
- plural_id:
- translations:
- - "Grupos com acesso a <strong>%{project_name}</strong>"
- - "<strong>%{project_name}</strong>へのアクセス権を持つグループ"
- - "可以访问 <strong>%{project_name}</strong>"
- - "Групи з доступом до <strong>%{project_name}</strong>"
- - "Los grupos con acceso a <strong>%{project_name}</strong>"
-"This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user.":
- plural_id:
- translations:
- - "このコミットは検証済みの署名でサインされています。しかしコミッターのメールは、 同じユーザーのものと<strong>検証されていません</strong>。"
- - "Этот коммит был подписан верифицированной подписью, но <strong>не подтверждена</strong> принадлежность электронной почты коммитеру."
- - "此提交已使用经过验证的签名进行签名,但<strong>未验证</strong>的提交者电子邮件属于同一用户。"
- - "Цей коміт підписано перевіреним підписом але адреса електронної пошти комітера <strong>не гарантовано</strong> належить тому самому користувачу."
- - "Este commit fue firmado con una firma verificada, pero <strong>no se ha verificado</strong> si la dirección de correo electrónico del commiter y la firma pertenecen al mismo usuario."
"This commit was signed with an <strong>unverified</strong> signature.":
plural_id:
translations:
@@ -831,229 +240,76 @@
- "此提交使用 <strong>已验证</strong> 的签名进行签名,并且已验证提交者的电子邮件属于同一用户。"
- "Цей коміт підписано <strong>перевіреним</strong> підписом і адреса електронної пошти комітера гарантовано належить тому самому користувачу."
- "Este commit fue firmado con una firma verificada, y <strong>se ha verificado</strong> que la dirección de correo electrónico del committer y la firma pertenecen al mismo usuario."
-"To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed.":
- plural_id:
- translations:
- - "パフォーマンス維持のため、 <strong>%{real_size} 個中 %{display_size} 個</strong> のファイルのみが表示されています。"
- - "为了保持性能,仅显示文件中的 <strong>%{display_size}/%{real_size}</strong>。"
- - "Для збереження швидкодії відображаються лише <strong>%{display_size} із %{real_size}</strong> файлів."
- - "Para mantener el rendimiento, solo se muestran <strong>%{display_size} de %{real_size}</strong> archivos."
-"This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>":
- plural_id:
- translations:
- - "このパイプラインは、<b>Auto DevOps</b>によって有効化された定義済みのCI/CD構成を利用します。"
- - "此流水线使用了 <b>Auto DevOps 预先定义的并已启用的 CI/CD 配置。</b>"
- - "Цей конвеєр використовує попередньо визначену конфігурацію CI / CD, увімкнену за допомогою <b>Auto DevOps</b>"
- - "Este pipeline utiliza una configuración de CI/CD predefinida habilitada por <b>Auto DevOps.</b>"
-"This project will live in your group <strong>%{namespace}</strong>. A project is where you house your files (repository), plan your work (issues), publish your documentation (wiki), and so much more.":
- plural_id:
- translations:
-"To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>.":
- plural_id:
- translations:
- - "Para usar apenas recursos CI/CD para um repositório externo, escolha <strong>CI/CD para repo externo</strong>."
- - "外部リポジトリにのみ CI/CD の機能を使用するには、<strong>外部リポジトリ用 CI/CD</strong>を選択してください。"
- - "要仅为外部仓库使用CI / CD功能时,请选择</strong>使用外部仓库运行CI/CD<strong>。"
- - "Щоб використовувати лише функції CI/CD для зовнішнього репозиторію, виберіть <strong>CI/CD для зовнішнього репозиторію</strong>."
- - "Um die CI/CD-Funktionen nur für ein externes Repository zu verwenden, wähle <strong>CI/CD für externes Repo</strong> aus."
- - "CI/CD 기능만 외부 저장소를 위해 사용할 수 있습니다, <strong>외부 저장소용 CI/CD 저장소</strong>를 선택하십시오."
- - "Pour n’utiliser uniquement que les fonctionnalités d’intégration et livraison continues (CI / CD) pour un dépôt externe, choisissez <strong>Intégration et livraison continues (CI / CD) pour dépôt externe</strong>."
- - "Para utilizar únicamente las funciones de CI/CD en un repositorio externo, seleccione <strong>CI/CD para un repositorio externo</strong>."
- - "Harici bir depoda yalnızca CI/CD özelliklerini kullanmak için <strong>Harici depo için CI/CD</strong>'yi seçin."
-"SlackIntegration|Paste the <strong>Webhook URL</strong> into the field below.":
- plural_id:
- translations:
- - "Вставьте <strong>URL веб-обработчика</strong> в поле ниже."
-"SlackIntegration|Select events below to enable notifications. The <strong>Slack channel names</strong> and <strong>Slack username</strong> fields are optional.":
- plural_id:
- translations:
-"SlackIntegration|<strong>Note:</strong> Usernames and private channels are not supported.":
- plural_id:
- translations:
- - "<strong> 注: </strong> ユーザー名とプライベートチャネルはサポートしていません。"
- - "<strong>Примечание:</strong> Имена пользователей и частные каналы не поддерживаются."
-"SlackService|2. Paste the <strong>Token</strong> into the field below":
- plural_id:
- translations:
- - "2. <strong>トークン</strong> を下のフィールドに貼り付けます"
- - "2. 将<strong>Token</strong>粘贴到下面的字段中"
- - "2. Вставте <strong>Токен</strong> у поле нижче"
- - "2. Pegue el <strong>Token</strong> en el campo que se muestra a continuación"
-"SlackService|3. Select the <strong>Active</strong> checkbox, press <strong>Save changes</strong> and start using GitLab inside Slack!":
- plural_id:
- translations:
- - "3. <strong>アクティブな</strong> チェックボックスを選択し、 <strong>変更を保存</strong> を押して、Slack内でGitLabの使用を開始します!"
- - "Выберите флажок <strong>Активный</strong>, нажмите на <strong>Сохранить изменения</strong> и начните использовать GitLab внутри Slack!"
- - "3. 选择<strong>Active</strong>复选框,点击<strong>Save change</strong>后开始在Slack中使用GitLab!"
- - "3. Встановіть прапорець в пункті <strong>Активний</strong>, натисніть <strong>Зберегти зміни</strong> та починайте використовувати GitLab в Slack!"
-"Finish setting up your dedicated account for <strong>%{group_name}</strong>.":
- plural_id:
- translations:
- - "Termine de configurar sua conta dedicada para <strong>%{group_name}</strong>."
- - "<strong>%{group_name} </strong>の専用アカウントの設定を完了してください。"
- - "Завершите настройку вашей учетной записи для <strong>%{group_name}</strong>."
- - "完成您的<strong>%{group_name}</strong>专用帐户设置。"
- - "Завершіть налаштування вашого виділеного облікового запису для <strong>%{group_name}</strong>."
- - "Finalizar la configuración de su cuenta dedicada para <strong>%{group_name}</strong>."
-"Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>":
- plural_id:
- translations:
- - "Preencha nos campos abaixo, ative o <strong>%{enable_label}</strong> e pressione <strong>%{save_changes}</strong>"
- - "下記項目に必要事項を入力し、 <strong>%{enable_label}</strong>をオンにして、 <strong>%{save_changes}</strong>を押してください。"
- - "填写下面的字段,启用<strong>%{enable_label}</strong>,然后点击<strong>%{save_changes}</strong>"
- - "Заповніть поля нижче, увімкніть <strong>%{enable_label}</strong> та натисніть <strong>%{save_changes}</strong>"
- - "Fülle die Felder unten aus, schalte <strong>%{enable_label}</strong> an, und drücke <strong>%{save_changes}</strong>"
- - "Renseignez les champs ci‐dessous, activez <strong>%{enable_label}</strong> et appuyez sur <strong>%{save_changes}</strong>"
- - "Rellene los siguientes campos, active <strong>%{enable_label}</strong>y presione <strong>%{save_changes}</strong>"
-"Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider":
- plural_id:
- translations:
- - "Compartilhe o <strong>%{sso_label}</strong> com membros de forma que eles possam entrar em seu grupo por meio de seu provedor de identidade"
- - "メンバーと<strong>%{sso_label}</strong>を共有すると、自身の ID プロバイダーでサインインすることができます"
- - "分享<strong>%{sso_label}</strong> 给组员,以便他们可以通过您的身份提供商登录您的群组"
- - "Поділіться <strong>%{sso_label}</strong> із учасниками для того, щоб вони могли увійти до вашої групи через провайдера ідентифікації"
- - "Teile das <strong>%{sso_label}</strong> mit Mitgliedern, damit sie sich über deinen Identitätsanbieter bei deiner Gruppe anmelden können"
- - "회원과 <strong>%{sso_label}</strong> 항목을 공유해 신원 제공 업체를 통해 그룹에 로그인할 수 있도록 합니다."
- - "Partager le <strong>%{sso_label}</strong> avec les membres afin qu’ils puissent se connecter à votre groupe via votre fournisseur d’identité"
- - "Comparta <strong>%{sso_label}</strong> con los diferentes miembros para que puedan iniciar sesión en su grupo a través de su proveedor de identidad"
-"<strong>%{label_name}</strong> <span>will be permanently deleted from %{subject_name}. This cannot be undone.</span>":
- plural_id:
- translations:
-"BillingPlans|Your GitLab.com %{plan} trial will <strong>expire after %{expiration_date}</strong>. You can retain access to the %{plan} features by upgrading below.":
- plural_id:
- translations:
- - "GitLab.com の %{plan} の試用版は、<strong>%{expiration_date}後に有効期限が切れます</strong>。 以下をアップグレードして、%{plan} の機能へのアクセスをそのままにできます。"
- - "您的GitLab.com%{plan}试用将在<strong>%{expiration_date}过期</strong>。您可以通过以下方式升级以保留对%{plan}功能的访问权限。"
- - "Ваш пробний період використання GitLab.com %{plan} закінчується <strong>%{expiration_date}</strong>. Ви можете зберегти доступ до функцій %{plan} шляхом оновленням нижче."
- - "Su periodo de prueba de GitLab.com %{plan} <strong>caducará después del %{expiration_date}</strong>. Puede conservar el acceso a las %{plan} funciones actualizando la versión %{plan} de GitLab.com a continuación."
-"ClusterIntegration|A cluster management project can be used to run deployment jobs with Kubernetes <code>cluster-admin</code> privileges.":
- plural_id:
- translations:
- - "クラスター管理プロジェクトを使用して、Kubernetes <code> cluster-admin </code>特権で展開ジョブを実行できます。"
- - "Проект управления кластером можно использовать для запуска заданий развертывания с привилегиями Kubernetes <code>cluster-admin</code>."
- - "集群管理的项目可以通过Kubernetes<code>cluster-admin</code>权限来运行部署作业。"
- - "Проєкт управління кластером може використовуватися для виконання завдань розгортання з привілеями Kubernetes <code>cluster-admin</code>."
-"Contributions for <strong>%{calendar_date}</strong>":
- plural_id:
- translations:
- - "Contribuições para <strong>%{calendar_date}</strong>"
- - "<strong>%{calendar_date}</strong>の貢献"
- - "<strong>%{calendar_date}</strong>的贡献"
- - "Внески за <strong>%{calendar_date}</strong>"
- - "Beiträge am <strong>%{calendar_date}</strong>"
- - "<strong>%{calendar_date}</strong>의 기여도"
- - "Contributions du <strong>%{calendar_date}</strong>"
- - "Contribuciones para <strong>%{calendar_date}</strong>"
- - "<strong>%{calendar_date}</strong> için katkılar"
-"Geo|You are on a secondary, <b>read-only</b> Geo node. If you want to make changes, you must visit this page on the %{primary_node}.":
- plural_id:
- translations:
- - "Você está em um nó Geo secundário <b>somente para leitura</b>. Se você quiser fazer mudanças, você deve visitar essa página no %{primary_node}."
- - "あなたは<b>読み取り専用の</b>セカンダリGeoノードにいます。変更を加えたい場合は、%{primary_node} のこのページにアクセスする必要があります。"
- - "Вы находитесь на вторичном узле Geo в режиме <b>только для чтения</b>. Если вы хотите внести изменения, вы должны зайти на эту же страницу на %{primary_node}."
- - "当前正在访问Geo次要 <b>只读 </b>节点。如需进行任何写入操作,必须访问%{primary_node}。"
- - "Ви знаходитесь на вторинному <b>лише для читання</b> Geo-вузлі. Якщо ви хочете внести будь-які зміни, ви повинні відвідати %{primary_node}."
- - "Du befindest dich auf einem sekundären, <b>read-only</b> Geo-Knoten. Um Änderungen vorzunehmen, musst du diese Seite auf dem %{primary_node} aufrufen."
- - "Vous êtes sur un nœud Geo secondaire <b>en lecture seule</b>. Si vous voulez apporter des modifications, vous devez le faire depuis cette page sur le %{primary_node}."
- - "Está en un nodo Geo secundario de <b>sólo lectura</b>. Si quiere realizar cambios, debe visitar esta página en %{primary_node}."
-"Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to make a limited amount of changes or perform a limited amount of actions on this page.":
- plural_id:
- translations:
- - "Você está em um nó Geo secundário <b>somente para leitura </b>. Você pode ser capaz de fazer um número limitado de mudanças ou executar uma quantidade limitada de ações nessa página."
- - "あなたは<b>読み取り専用の</b>セカンダリGeoノードにいます。このページでは限定的な変更・操作をすることができます。"
- - "Вы находитесь на вторичном, доступном <b>только для чтения</b>, узле Geo. Вы можете осуществлять ограниченное количество изменений и действий на данной странице."
- - "当前正在访问Geo次要 <b>只读 </b>节点。您可以在此页面上进行有限的更改或执行有限的操作。"
- - "Ви знаходитесь на вторинному <b>лише для читання</b> Geo-вузлі. Ви зможете вносити лише обмежену кількість змін та виконувати обмежений набір операцій з цієї сторінки."
- - "Du befindest dich auf einem sekundären, <b>read-only</b> Geo-Knoten. Möglicherweise kannst du nur eine begrenzte Anzahl an Änderungen oder Aktionen auf dieser Seite durchführen."
- - "Vous êtes sur un nœud Geo secondaire <b>en lecture seule</b>. Nous ne pourrez effectuer que des modifications ou des actions limitées depuis cette page."
- - "Está en un nodo secundario, <b>de solo lectura</b> Geo. Es posible que pueda realizar una cantidad limitada de cambios o realizar una cantidad limitada de acciones en esta página."
-"In order to tailor your experience with GitLab we<br>would like to know a bit more about you.":
- plural_id:
- translations:
- - "GitLabでの経験を調整するために、<br>もう少しあなたについて知りたいです。"
- - "为了能量身定制您在GitLab的体验,我们<br>希望对您有更多了解。"
- - "Для того, щоб адаптувати GitLab до вас нам<br>потрібно більше про вас дізнатися."
- - "Para personalizar su experiencia con GitLab <br>nos gustaría conocer un poco más sobre usted."
-"Welcome to GitLab.com<br>@%{name}!":
- plural_id:
- translations:
- - "Добро пожаловать в GitLab.com<br>@%{name}!"
- - "Ласкаво просимо до GitLab.com<br>@%{name}!"
- - "¡Bienvenido a GitLab.com<br>@%{name}!"
-"In order to personalize your experience with GitLab<br>we would like to know a bit more about you.":
- plural_id:
- translations:
- - "Для того, чтобы адаптировать ваш опыт работы с GitLab<br>, мы хотели бы узнать о вас немного больше."
- - "Con el fin de adaptar su experiencia con GitLab<br>nos gustaría saber un poco más sobre usted."
- - "Для того, щоб персоналізувати ваш досвід роботи з GitLab<br>,ми хотіли б дізнатися більше про вас."
-"Please refer to <a href=\\\"%{docs_url}\\\">%{docs_url}</a>":
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}":
plural_id:
translations:
- - "<a href=\\\"%{docs_url}\\\">%{docs_url}</a>を参照してください"
- - "请参考<a href=\\\"%{docs_url}\\\">%{docs_url}</a>"
- - "Будь ласка перегляньте <a href=\\\"%{docs_url}\\\">%{docs_url}</a>"
- - "Por favor, consulte <a href=\\\"%{docs_url}\\\">%{docs_url}</a>"
-"SSH host keys are not available on this system. Please use <code>ssh-keyscan</code> command or contact your GitLab administrator for more information.":
+ - "分支 <strong>%{branch_name}</strong> 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}"
+ - "O branch <strong>%{branch_name}</strong> foi criado. Para configurar o deploy automático, selecione um modelo de Yaml do GitLab CI e commit suas mudanças. %{link_to_autodeploy_doc}"
+ - "<strong>%{branch_name}</strong> ブランチが作成されました。自動デプロイを設定するには、GitLab CI Yaml テンプレートを選択して、変更をコミットしてください。 %{link_to_autodeploy_doc}"
+ - "La branch <strong>%{branch_name}</strong> è stata creata. Per impostare un rilascio automatico scegli un template CI di Gitlab e committa le tue modifiche %{link_to_autodeploy_doc}"
+ - "O ramo <strong>%{branch_name}</strong> foi criado. Para configurar a implantação automática, seleciona um modelo de Yaml do GitLab CI e envia as tuas alterações. %{link_to_autodeploy_doc}"
+ - "Ветка <strong>%{branch_name}</strong> создана. Для настройки автоматического развертывания выберите YAML-шаблон для GitLab CI и зафиксируйте свои изменения. %{link_to_autodeploy_doc}"
+ - "已创建分支 <strong>%{branch_name}</strong> 。如需设置自动部署, 请选择合适的 GitLab CI Yaml 模板并提交更改。%{link_to_autodeploy_doc}"
+ - "Гілка <strong>%{branch_name}</strong> створена. Для настройки автоматичного розгортання виберіть GitLab CI Yaml-шаблон і закомітьте зміни. %{link_to_autodeploy_doc}"
+ - "Клонът <strong>%{branch_name}</strong> беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}"
+ - "Branch <strong>%{branch_name}</strong> wurde erstellt. Um die automatische Bereitstellung einzurichten, wähle eine GitLab CI Yaml Vorlage und committe deine Änderungen. %{link_to_autodeploy_doc}"
+ - "<strong>%{branch_name}</strong> 브랜치가 생성되었습니다. 자동 배포를 설정하려면 GitLab CI Yaml 템플릿을 선택하고 변경 사항을 적용하십시오. %{link_to_autodeploy_doc}"
+ - "La branĉo <strong>%{branch_name}</strong> estis kreita. Por agordi aŭtomatan disponigadon, bonvolu elekti Yaml-ŝablonon por GitLab CI kaj enmeti viajn ŝanĝojn. %{link_to_autodeploy_doc}"
+ - "La branche <strong>%{branch_name}</strong> a été créée. Pour mettre en place le déploiement automatisé, sélectionnez un modèle de fichier YAML pour l’intégration continue (CI) de GitLab, et validez les modifications. %{link_to_autodeploy_doc}"
+ - "La rama <strong>%{branch_name}</strong> fue creada. Para configurar el auto despliegue, escoge una plantilla Yaml para GitLab CI y envía tus cambios. %{link_to_autodeploy_doc}"
+"GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project's %{strong_start}Settings > General > Visibility%{strong_end} page.":
plural_id:
translations:
- - "このシステムでは SSH ホスト鍵は使用できません。 <code>ssh-keyscan</code> コマンドを使用するか、GitLab の管理者に詳細をお問い合わせください。"
- - "Ключи SSH хоста недоступны в этой системе. Пожалуйста, используйте команду <code>ssh-keyscan</code> или свяжитесь со своим администратором GitLab для получения дополнительной информации."
- - "SSH主机密钥在此系统上不可用。请使用<code>ssh-keyscan</code>命令或与您的GitLab管理员联系以获取更多信息。"
- - "SSH-ключі хоста не доступні в цій системі. Будь ласка, використовуйте команду <code>ssh-keyscan</code> або зверніться до вашого адміністратора GitLab для додаткової інформації."
- - "Las claves SSH del host no se encuentran disponibles en este sistema. Utilice el comando <code>ssh-keyscan</code> o póngase en contacto con su administrador de GitLab para obtener más información."
-"Speed up your DevOps<br>with GitLab":
+ - "GitLab Pagesはこのプロジェクトでは無効になっています。 プロジェクトの%{strong_start} 設定> 全般> 可視性%{strong_end}ページで有効にできます。"
+ - "GitLab Pages отключены для этого проекта. Вы можете включить в поле %{strong_start}Настройки > Общие > Видимость%{strong_end} вашего проекта."
+ - "此项目禁用GitLab Pages。您可以在您的项目的%{strong_start}设置 > 常规 > 可见性%{strong_end} 页面启用。"
+ - "GitLab Pages вимкнено для цього проєкту. Ви можете їх увімкнути перейшовши на сторінку проєкту %{strong_start}Налаштування > Загальні > Видимість%{strong_end}."
+ - "Las páginas de GitLab están deshabilitadas para este proyecto. Puede habilitarlas en los ajustes %{strong_start} de su proyecto > General > Visibilidad%{strong_end}."
+"You can invite a new member to <strong>%{project_name}</strong> or invite another group.":
plural_id:
translations:
- - "Acelere sus DevOps<br>con GitLab"
-"The Git LFS objects will <strong>not</strong> be synced.":
+ - "新しいメンバーを<strong>%{project_name} </strong>に招待するか、別のグループを招待することができます。"
+ - "Podes convidar um novo para <strong>%{project_name}</strong> ou convidar outro grupo."
+ - "邀请新成员或另一个群组加入<strong>%{project_name}</strong>。"
+ - "Puede invitar a un nuevo miembro a <strong>%{project_name}</strong> o invitar a otro grupo."
+ - "<strong>%{project_name}</strong> projesine yeni bir üye davet edebilir veya başka bir grubu davet edebilirsiniz."
+ - "Вы можете пригласить нового участника в <strong>%{project_name}</strong> или пригласить другую группу."
+ - "Ви можете запросити нового учасника до <strong>%{project_name}</strong> або запросити іншу групу."
+"You can invite a new member to <strong>%{project_name}</strong>.":
plural_id:
translations:
- - "Os objetos LFS do Git <strong>não</strong> serão sincronizados."
- - "Git の LFS オブジェクトは<strong>同期しません</strong>。"
- - "Git LFS对象将<strong>不会</strong>被同步。"
- - "Об'єкти Git LFS <strong>не</strong> будуть синхронізуватися."
- - "Die Git LFS-Objekte werden <strong>nicht</strong> synchronisiert werden."
- - "Les objets Git LFS <strong>ne sont pas</strong> synchronisés."
- - "Los objetos Git LFS <strong>no</strong> serán sincronizados."
- - "Obiekty Git LFS <strong>nie będą</strong> zsynchronizowane."
-"This %{issuable} is locked. Only <strong>project members</strong> can comment.":
+ - "新しいメンバーを<strong>%{project_name} </strong>に招待できます。"
+ - "Podes convidar um novo membro para <strong>%{project_name}</strong>."
+ - "邀请新成员加入<strong>%{project_name}</strong>。"
+ - "Puedes invitar a un nuevo miembro a <strong>%{project_name}</strong>."
+ - "<strong>%{project_name}</strong> projesine yeni bir üye davet edebilirsiniz."
+ - "Вы можете пригласить нового участника в <strong>%{project_name}</strong>."
+ - "Ви можете запросити нового учасника до <strong>%{project_name}</strong>."
+"You can invite another group to <strong>%{project_name}</strong>.":
plural_id:
translations:
- - "この %{issuable} はロックされています。<strong>プロジェクトメンバー</strong> だけがコメントできます。"
- - "Этот %{issuable} заблокировано. Только <strong>участники проекта</strong> могут комментировать."
- - "此%{issuable}已被锁定。只有<strong>项目成员</strong>可以发表评论。"
- - "Ця %{issuable} заблокована. Лише <strong>учасники проекту</strong> можуть коментувати."
- - "Este %{issuable} está bloqueado. Solo los <strong>miembros del proyecto</strong> pueden comentar."
-"Upon performing this action, the contents of this group, its subgroup and projects will be permanently removed after %{deletion_adjourned_period} days on <strong>%{date}</strong>. Until that time:":
+ - "他のグループを<strong>%{project_name} </strong>に招待できます。"
+ - "Podes convidar outro grupo para <strong>%{project_name}</strong>."
+ - "您可以邀请另一个群组加入<strong>%{project_name}</strong>。"
+ - "Ви можете запросити нову групу до <strong>%{project_name}</strong>."
+ - "Puedes invitar a otro grupo a <strong>%{project_name}</strong>."
+"Example: <code>192.168.0.0/24</code>. %{read_more_link}.":
plural_id:
translations:
-"Use <code>%{native_redirect_uri}</code> for local tests":
+"Note that PostgreSQL %{pg_version_upcoming} will become the minimum required version in GitLab %{gl_version_upcoming} (%{gl_version_upcoming_date}). Please consider upgrading your environment to a supported PostgreSQL version soon, see <a href=\\\"%{pg_version_upcoming_url}\\\">the related epic</a> for details.":
plural_id:
translations:
- - "Use <code>%{native_redirect_uri}</code> para testes locais"
- - "ローカルテストに <code>%{native_redirect_uri}</code> を使用"
- - "使用<code>%{native_redirect_uri}</code>进行本地测试"
- - "Використовувати <code>%{native_redirect_uri}</code> для локальних тестів"
- - "Verwende <code>%{native_redirect_uri}</code> für lokale Tests"
- - "Utiliser <code>%{native_redirect_uri}</code> pour les tests locaux"
- - "Use <code>%{native_redirect_uri}</code> para los tests locales"
-"WikiMarkdownTip|To link to a (new) page, simply type <code class=\\\"js-markup-link-example\\\">%{link_example}</code>":
+"Authorize <strong>%{user}</strong> to use your account?":
plural_id:
translations:
-"You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution.":
+"DeployFreeze|Specify times when deployments are not allowed for an environment. The <code>gitlab-ci.yml</code> file must be updated to make deployment jobs aware of the %{freeze_period_link_start}freeze period%{freeze_period_link_end}.":
plural_id:
translations:
- - "Você é um administrador, o que significa que conceder acesso a <strong>%{client_name}</strong> permitirá que eles também interajam com o GitLab como administrador. Prossiga com cuidado."
- - "あなたは管理者です。つまり、 <strong>%{client_name}</strong> へのアクセスを許可すると、それは管理者としてGitLabとやりとりできます。注意してください。"
- - "您是一名管理员,这意味着授予对 <strong>%{client_name}</strong> 访问权限将允许他们作为管理员与GitLab进行交互。请谨慎操作。"
- - "Ви — адміністратор, а це означає, що надання доступу для <strong>%{client_name}</strong> дозволить їм взаємодіяти з GitLab як адміністратору. Продовжуйте обережно."
- - "Du bist Administrator(in). Wenn du <strong>%{client_name}</strong> Zugriff gewährst, wird es auch als Administrator mit GitLab interagieren können. Mit Vorsicht fortfahren."
- - "Vous êtes un administrateur ou une administratrice, ce qui signifie qu’accorder un accès à <strong>%{client_name}</strong> lui permettra d’interagir avec GitLab en tant qu’administrateur également. Faites‐le avec prudence."
- - "Es administrador, lo que significa que otorgar acceso a <strong>%{client_name}</strong> le permitirá interactuar con GitLab como también administrador. Por favor, proceda con precaución."
-"You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>.":
- plural_id:
+"<project name>":
translations:
- - "変数名の前に <code>K8S_SECRET_</code>を指定することで、実行中のアプリケーションで使用する変数を追加することもできます。"
- - "您还可以通过在变量键前面加上<code>K8S_SECRET_</code>来添加可用于正在运行的应用程序的变量。"
- - "Ви також можете додати змінні, що будуть доступними для запущеного застосунку шляхом додавання префіксу <code>K8S_SECRET_</code> до їх імен."
- - "También puede añadir variables que están disponibles para la aplicación en ejecución, anteponiendo la clave de variable con <code>K8S_SECRET_</code>."
+ - "<название проекта>"
+ - "<project name>"
+ - "<proje adı>"
+ - "<naziv projekta>"
+ - "<ім’я проєкту>"
+ - "<프로젝트 이름>"
diff --git a/lib/gitlab/markdown_cache.rb b/lib/gitlab/markdown_cache.rb
index ac3492dbe33..da3b597a74e 100644
--- a/lib/gitlab/markdown_cache.rb
+++ b/lib/gitlab/markdown_cache.rb
@@ -3,7 +3,7 @@
module Gitlab
module MarkdownCache
# Increment this number every time the renderer changes its output
- CACHE_COMMONMARK_VERSION = 24
+ CACHE_COMMONMARK_VERSION = 25
CACHE_COMMONMARK_VERSION_START = 10
BaseError = Class.new(StandardError)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e7e42175574..b3d522d8817 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1072,9 +1072,6 @@ msgstr ""
msgid "< 1 hour"
msgstr ""
-msgid "<project name>"
-msgstr ""
-
msgid "<strong>Deletes</strong> source branch"
msgstr ""
@@ -3554,7 +3551,7 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
-msgid "Authorize <strong>%{user}</strong> to use your account?"
+msgid "Authorize %{user} to use your account?"
msgstr ""
msgid "Authorize external services to send alerts to GitLab"
@@ -3953,7 +3950,7 @@ msgstr ""
msgid "Branch %{branchName} was not found in this project's repository."
msgstr ""
-msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
+msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
msgstr ""
msgid "Branch has changed"
@@ -5105,19 +5102,16 @@ msgstr ""
msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
msgstr ""
-msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
-msgstr ""
-
msgid "ClusterIntegration| This will permanently delete the following resources: <ul> <li>All installed applications and related resources</li> <li>The <code>gitlab-managed-apps</code> namespace</li> <li>Any project namespaces</li> <li><code>clusterroles</code></li> <li><code>clusterrolebindings</code></li> </ul>"
msgstr ""
-msgid "ClusterIntegration| can be used instead of a custom domain."
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
+msgid "ClusterIntegration|%{externalIp}.nip.io"
msgstr ""
-msgid "ClusterIntegration|%{external_ip}.nip.io"
+msgid "ClusterIntegration|%{linkStart}More information%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|%{title} uninstalled successfully."
@@ -5171,7 +5165,7 @@ msgstr ""
msgid "ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster. %{startLink}More information%{endLink}"
msgstr ""
-msgid "ClusterIntegration|Alternatively"
+msgid "ClusterIntegration|Alternatively, "
msgstr ""
msgid "ClusterIntegration|Amazon EKS"
@@ -5234,9 +5228,6 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
-msgid "ClusterIntegration|Choose which of your environments will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
-msgstr ""
-
msgid "ClusterIntegration|Clear cluster cache"
msgstr ""
@@ -5858,7 +5849,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
msgstr ""
-msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
+msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{linkStart}Auto DevOps.%{linkEnd} The domain should have a wildcard DNS configured matching the domain. "
msgstr ""
msgid "ClusterIntegration|Subnets"
@@ -5981,6 +5972,9 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
+msgid "ClusterIntegration|can be used instead of a custom domain. "
+msgstr ""
+
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -8097,7 +8091,7 @@ msgstr ""
msgid "DeployFreeze|No deploy freezes exist for this project. To add one, click %{strongStart}Add deploy freeze%{strongEnd}"
msgstr ""
-msgid "DeployFreeze|Specify times when deployments are not allowed for an environment. The <code>gitlab-ci.yml</code> file must be updated to make deployment jobs aware of the %{freeze_period_link_start}freeze period%{freeze_period_link_end}."
+msgid "DeployFreeze|Specify times when deployments are not allowed for an environment. The %{filename} file must be updated to make deployment jobs aware of the %{freeze_period_link_start}freeze period%{freeze_period_link_end}."
msgstr ""
msgid "DeployFreeze|Time zone"
@@ -9959,7 +9953,7 @@ msgstr ""
msgid "Exactly one of %{attributes} is required"
msgstr ""
-msgid "Example: <code>192.168.0.0/24</code>. %{read_more_link}."
+msgid "Example: %{ip_address}. %{read_more_link}."
msgstr ""
msgid "Example: @sub\\.company\\.com$"
@@ -11509,7 +11503,7 @@ msgstr ""
msgid "GitLabPages|Force HTTPS (requires valid certificates)"
msgstr ""
-msgid "GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project's %{strong_start}Settings > General > Visibility%{strong_end} page."
+msgid "GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project's %{strong_start}Settings &gt; General &gt; Visibility%{strong_end} page."
msgstr ""
msgid "GitLabPages|It may take up to 30 minutes before the site is available after the first deployment."
@@ -12851,6 +12845,9 @@ msgstr ""
msgid "ImportProjects|Importing the project failed: %{reason}"
msgstr ""
+msgid "ImportProjects|Requesting namespaces failed"
+msgstr ""
+
msgid "ImportProjects|Requesting your %{provider} repositories failed"
msgstr ""
@@ -14960,6 +14957,9 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
+msgid "Merge locally"
+msgstr ""
+
msgid "Merge options"
msgstr ""
@@ -16535,7 +16535,7 @@ msgstr ""
msgid "Note parameters are invalid: %{errors}"
msgstr ""
-msgid "Note that PostgreSQL %{pg_version_upcoming} will become the minimum required version in GitLab %{gl_version_upcoming} (%{gl_version_upcoming_date}). Please consider upgrading your environment to a supported PostgreSQL version soon, see <a href=\"%{pg_version_upcoming_url}\">the related epic</a> for details."
+msgid "Note that PostgreSQL %{pg_version_upcoming} will become the minimum required version in GitLab %{gl_version_upcoming} (%{gl_version_upcoming_date}). Please consider upgrading your environment to a supported PostgreSQL version soon, see %{pg_version_upcoming_url_open}the related epic%{pg_version_upcoming_url_close} for details."
msgstr ""
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
@@ -20798,9 +20798,15 @@ msgstr ""
msgid "Resolve all threads in new issue"
msgstr ""
+msgid "Resolve conflicts"
+msgstr ""
+
msgid "Resolve conflicts on source branch"
msgstr ""
+msgid "Resolve these conflicts or ask someone with write access to this repository to merge it locally."
+msgstr ""
+
msgid "Resolve thread"
msgstr ""
@@ -24144,6 +24150,9 @@ msgstr ""
msgid "The commit does not exist"
msgstr ""
+msgid "The comparison view may be inaccurate due to merge conflicts."
+msgstr ""
+
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
@@ -24467,6 +24476,9 @@ msgstr ""
msgid "There are currently no events."
msgstr ""
+msgid "There are merge conflicts"
+msgstr ""
+
msgid "There are no %{replicableTypeName} to show"
msgstr ""
@@ -25686,9 +25698,6 @@ msgstr ""
msgid "Today"
msgstr ""
-msgid "Toggle Kubernetes cluster"
-msgstr ""
-
msgid "Toggle Markdown preview"
msgstr ""
@@ -27810,7 +27819,7 @@ msgstr ""
msgid "You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico."
msgstr ""
-msgid "You are using PostgreSQL %{pg_version_current}, but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. Please upgrade your environment to a supported PostgreSQL version, see %{pg_requirements_url} for details."
+msgid "You are using PostgreSQL %{pg_version_current}, but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. Please upgrade your environment to a supported PostgreSQL version, see %{pg_requirements_url_open}database requirements%{pg_requirements_url_close} for details."
msgstr ""
msgid "You can %{linkStart}view the blob%{linkEnd} instead."
@@ -27867,13 +27876,13 @@ msgstr ""
msgid "You can get started by cloning the repository or start adding files to it with one of the following options."
msgstr ""
-msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
+msgid "You can invite a new member to %{project_name} or invite another group."
msgstr ""
-msgid "You can invite a new member to <strong>%{project_name}</strong>."
+msgid "You can invite a new member to %{project_name}."
msgstr ""
-msgid "You can invite another group to <strong>%{project_name}</strong>."
+msgid "You can invite another group to %{project_name}."
msgstr ""
msgid "You can move around the graph by using the arrow keys."
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index 0887fe62b9c..3bb51d2d579 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -12,9 +12,6 @@ module QA
view 'app/assets/javascripts/clusters/forms/components/integration_form.vue' do
element :integration_status_toggle, required: true
- end
-
- view 'app/views/clusters/clusters/_gitlab_integration_form.html.haml' do
element :base_domain_field, required: true
element :save_changes_button, required: true
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 8b113105cd6..a0e478ef368 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -181,10 +181,11 @@ RSpec.describe Projects::IssuesController do
project.add_developer(user)
end
- it 'builds a new issue' do
+ it 'builds a new issue', :aggregate_failures do
get :new, params: { namespace_id: project.namespace, project_id: project }
expect(assigns(:issue)).to be_a_new(Issue)
+ expect(assigns(:issue).issue_type).to eq('issue')
end
where(:conf_value, :conf_result) do
@@ -214,6 +215,24 @@ RSpec.describe Projects::IssuesController do
end
end
+ context 'setting issue type' do
+ let(:issue_type) { 'issue' }
+
+ before do
+ get :new, params: { namespace_id: project.namespace, project_id: project, issue: { issue_type: issue_type } }
+ end
+
+ subject { assigns(:issue).issue_type }
+
+ it { is_expected.to eq('issue') }
+
+ context 'incident issue' do
+ let(:issue_type) { 'incident' }
+
+ it { is_expected.to eq(issue_type) }
+ end
+ end
+
it 'fills in an issue for a merge request' do
project_with_repository = create(:project, :repository)
project_with_repository.add_developer(user)
@@ -1049,6 +1068,14 @@ RSpec.describe Projects::IssuesController do
project.issues.first
end
+ it 'creates the issue successfully', :aggregate_failures do
+ issue = post_new_issue
+
+ expect(issue).to be_a(Issue)
+ expect(issue.persisted?).to eq(true)
+ expect(issue.issue_type).to eq('issue')
+ end
+
context 'resolving discussions in MergeRequest' do
let(:discussion) { create(:diff_note_on_merge_request).to_discussion }
let(:merge_request) { discussion.noteable }
@@ -1289,6 +1316,20 @@ RSpec.describe Projects::IssuesController do
end
end
end
+
+ context 'setting issue type' do
+ let(:issue_type) { 'issue' }
+
+ subject { post_new_issue(issue_type: issue_type)&.issue_type }
+
+ it { is_expected.to eq('issue') }
+
+ context 'incident issue' do
+ let(:issue_type) { 'incident' }
+
+ it { is_expected.to eq(issue_type) }
+ end
+ end
end
describe 'POST #mark_as_spam' do
diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb
index 6058c35c2cf..4f7f62d00a5 100644
--- a/spec/features/clusters/cluster_detail_page_spec.rb
+++ b/spec/features/clusters/cluster_detail_page_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe 'Clusterable > Show page' do
expect(page).to have_content(cluster_type_label)
end
- it 'allow the user to set domain' do
+ it 'allow the user to set domain', :js do
visit cluster_path
within '.js-cluster-integration-form' do
@@ -28,20 +28,19 @@ RSpec.describe 'Clusterable > Show page' do
click_on 'Save changes'
end
- expect(page.status_code).to eq(200)
expect(page).to have_content('Kubernetes cluster was successfully updated.')
end
- context 'when there is a cluster with ingress and external ip' do
+ context 'when there is a cluster with ingress and external ip', :js do
before do
cluster.create_application_ingress!(external_ip: '192.168.1.100')
visit cluster_path
end
- it 'shows help text with the domain as an alternative to custom domain' do
+ it 'shows help text with the domain as an alternative to custom domain', :js do
within '.js-cluster-integration-form' do
- expect(find(cluster_ingress_help_text_selector)).not_to match_css(hide_modifier_selector)
+ expect(find(cluster_ingress_help_text_selector).text).to include('192.168.1.100')
end
end
end
@@ -51,7 +50,7 @@ RSpec.describe 'Clusterable > Show page' do
visit cluster_path
within '.js-cluster-integration-form' do
- expect(find(cluster_ingress_help_text_selector)).to match_css(hide_modifier_selector)
+ expect(page).not_to have_selector(cluster_ingress_help_text_selector)
end
end
end
diff --git a/spec/features/merge_request/user_edits_merge_request_spec.rb b/spec/features/merge_request/user_edits_merge_request_spec.rb
index 6c5f508c8c6..364af8d8a76 100644
--- a/spec/features/merge_request/user_edits_merge_request_spec.rb
+++ b/spec/features/merge_request/user_edits_merge_request_spec.rb
@@ -85,13 +85,24 @@ RSpec.describe 'User edits a merge request', :js do
end
end
- it 'changes the target branch' do
- expect(page).to have_content('From master into feature')
+ describe 'changing target branch' do
+ it 'allows user to change target branch' do
+ expect(page).to have_content('From master into feature')
- select2('merge-test', from: '#merge_request_target_branch')
- click_button('Save changes')
+ select2('merge-test', from: '#merge_request_target_branch')
+ click_button('Save changes')
+
+ expect(page).to have_content("Request to merge #{merge_request.source_branch} into merge-test")
+ expect(page).to have_content("changed target branch from #{merge_request.target_branch} to merge-test")
+ end
- expect(page).to have_content("Request to merge #{merge_request.source_branch} into merge-test")
- expect(page).to have_content("changed target branch from #{merge_request.target_branch} to merge-test")
+ describe 'merged merge request' do
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project, state: :merged) }
+
+ it 'does not allow user to change target branch' do
+ expect(page).to have_content('From master into feature')
+ expect(page).not_to have_selector('.select2-container')
+ end
+ end
end
end
diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb
index 4c144037acd..1760ec880bc 100644
--- a/spec/features/projects/badges/coverage_spec.rb
+++ b/spec/features/projects/badges/coverage_spec.rb
@@ -63,7 +63,7 @@ RSpec.describe 'test coverage badge' do
create(:ci_pipeline, opts).tap do |pipeline|
yield pipeline
- pipeline.update_legacy_status
+ ::Ci::ProcessPipelineService.new(pipeline).execute
end
end
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
index 18437a36c21..d3277cdb7cc 100644
--- a/spec/frontend/clusters/clusters_bundle_spec.js
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -2,12 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
import { loadHTMLFixture } from 'helpers/fixtures';
import { setTestTimeout } from 'helpers/timeout';
import Clusters from '~/clusters/clusters_bundle';
-import {
- APPLICATION_STATUS,
- INGRESS_DOMAIN_SUFFIX,
- APPLICATIONS,
- RUNNER,
-} from '~/clusters/constants';
+import { APPLICATION_STATUS, APPLICATIONS, RUNNER } from '~/clusters/constants';
import axios from '~/lib/utils/axios_utils';
import initProjectSelectDropdown from '~/project_select';
@@ -308,7 +303,6 @@ describe('Clusters', () => {
return promise.then(() => {
expect(cluster.store.state.applications.helm.status).toEqual(INSTALLED);
expect(cluster.store.state.applications.helm.uninstallFailed).toBe(true);
-
expect(cluster.store.state.applications.helm.requestReason).toBeDefined();
});
});
@@ -334,10 +328,8 @@ describe('Clusters', () => {
describe('handleClusterStatusSuccess', () => {
beforeEach(() => {
jest.spyOn(cluster.store, 'updateStateFromServer').mockReturnThis();
- jest.spyOn(cluster, 'toggleIngressDomainHelpText').mockReturnThis();
jest.spyOn(cluster, 'checkForNewInstalls').mockReturnThis();
jest.spyOn(cluster, 'updateContainer').mockReturnThis();
-
cluster.handleClusterStatusSuccess({ data: {} });
});
@@ -349,53 +341,11 @@ describe('Clusters', () => {
expect(cluster.checkForNewInstalls).toHaveBeenCalled();
});
- it('toggles ingress domain help text', () => {
- expect(cluster.toggleIngressDomainHelpText).toHaveBeenCalled();
- });
-
it('updates message containers', () => {
expect(cluster.updateContainer).toHaveBeenCalled();
});
});
- describe('toggleIngressDomainHelpText', () => {
- let ingressPreviousState;
- let ingressNewState;
-
- beforeEach(() => {
- ingressPreviousState = { externalIp: null };
- ingressNewState = { externalIp: '127.0.0.1' };
- });
-
- describe(`when ingress have an external ip assigned`, () => {
- beforeEach(() => {
- cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
- });
-
- it('displays custom domain help text', () => {
- expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(false);
- });
-
- it('updates ingress external ip address', () => {
- expect(cluster.ingressDomainSnippet.textContent).toEqual(
- `${ingressNewState.externalIp}${INGRESS_DOMAIN_SUFFIX}`,
- );
- });
- });
-
- describe(`when ingress does not have an external ip assigned`, () => {
- it('hides custom domain help text', () => {
- ingressPreviousState.externalIp = '127.0.0.1';
- ingressNewState.externalIp = null;
- cluster.ingressDomainHelpText.classList.remove('hide');
-
- cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
-
- expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true);
- });
- });
- });
-
describe('updateApplication', () => {
const params = { version: '1.0.0' };
let storeUpdateApplication;
diff --git a/spec/frontend/clusters/forms/components/integration_form_spec.js b/spec/frontend/clusters/forms/components/integration_form_spec.js
index 2733749ecd3..37582c809b8 100644
--- a/spec/frontend/clusters/forms/components/integration_form_spec.js
+++ b/spec/frontend/clusters/forms/components/integration_form_spec.js
@@ -1,48 +1,112 @@
+import Vuex from 'vuex';
import IntegrationForm from '~/clusters/forms/components/integration_form.vue';
import { createStore } from '~/clusters/forms/stores/index';
-import { mount } from '@vue/test-utils';
-import { GlToggle } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlToggle, GlButton } from '@gitlab/ui';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
describe('ClusterIntegrationForm', () => {
let wrapper;
- let store;
- const glToggle = () => wrapper.find(GlToggle);
- const toggleButton = () => glToggle().find('button');
- const toggleInput = () => wrapper.find('input');
+ const defaultStoreValues = {
+ enabled: true,
+ editable: true,
+ environmentScope: '*',
+ baseDomain: 'testDomain',
+ applicationIngressExternalIp: null,
+ };
- const createWrapper = () => {
- store = createStore({
- enabled: 'true',
- editable: 'true',
+ const createWrapper = (storeValues = defaultStoreValues) => {
+ wrapper = shallowMount(IntegrationForm, {
+ localVue,
+ store: createStore(storeValues),
+ provide: {
+ autoDevopsHelpPath: 'topics/autodevops/index',
+ externalEndpointHelpPath: 'user/clusters/applications.md',
+ },
});
- wrapper = mount(IntegrationForm, { store });
- return wrapper.vm.$nextTick();
};
- beforeEach(() => {
- return createWrapper();
- });
+ const destroyWrapper = () => {
+ wrapper.destroy();
+ wrapper = null;
+ };
+
+ const findSubmitButton = () => wrapper.find(GlButton);
+ const findGlToggle = () => wrapper.find(GlToggle);
afterEach(() => {
- wrapper.destroy();
+ destroyWrapper();
});
- it('creates the toggle and label', () => {
- expect(wrapper.text()).toContain('GitLab Integration');
- expect(wrapper.contains(GlToggle)).toBe(true);
- });
+ describe('rendering', () => {
+ beforeEach(() => createWrapper());
+
+ it('enables toggle if editable is true', () => {
+ expect(findGlToggle().props('disabled')).toBe(false);
+ });
+ it('sets the envScope to default', () => {
+ expect(wrapper.find('[id="cluster_environment_scope"]').attributes('value')).toBe('*');
+ });
+
+ it('sets the baseDomain to default', () => {
+ expect(wrapper.find('[id="cluster_base_domain"]').attributes('value')).toBe('testDomain');
+ });
- it('initializes toggle with store value', () => {
- expect(toggleButton().classes()).toContain('is-checked');
- expect(toggleInput().attributes('value')).toBe('true');
+ describe('when editable is false', () => {
+ beforeEach(() => {
+ createWrapper({ ...defaultStoreValues, editable: false });
+ });
+
+ it('disables toggle if editable is false', () => {
+ expect(findGlToggle().props('disabled')).toBe(true);
+ });
+
+ it('does not render the save button', () => {
+ expect(findSubmitButton().exists()).toBe(false);
+ });
+ });
+
+ it('does not render external IP block if applicationIngressExternalIp was not passed', () => {
+ createWrapper({ ...defaultStoreValues });
+
+ expect(wrapper.find('.js-ingress-domain-help-text').exists()).toBe(false);
+ });
+
+ it('renders external IP block if applicationIngressExternalIp was passed', () => {
+ createWrapper({ ...defaultStoreValues, applicationIngressExternalIp: '127.0.0.1' });
+
+ expect(wrapper.find('.js-ingress-domain-help-text').exists()).toBe(true);
+ });
});
- it('switches the toggle value on click', () => {
- toggleButton().trigger('click');
- wrapper.vm.$nextTick(() => {
- expect(toggleButton().classes()).not.toContain('is-checked');
- expect(toggleInput().attributes('value')).toBe('false');
+ describe('reactivity', () => {
+ beforeEach(() => createWrapper());
+
+ it('enables the submit button on changing toggle to different value', () => {
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ // setData is a bad approach because it changes the internal implementation which we should not touch
+ // but our GlFormInput lacks the ability to set a new value.
+ wrapper.setData({ toggleEnabled: !defaultStoreValues.enabled });
+ })
+ .then(() => {
+ expect(findSubmitButton().props('disabled')).toBe(false);
+ });
+ });
+
+ it('enables the submit button on changing input values', () => {
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ wrapper.setData({ envScope: `${defaultStoreValues.environmentScope}1` });
+ })
+ .then(() => {
+ expect(findSubmitButton().props('disabled')).toBe(false);
+ });
});
});
});
diff --git a/spec/frontend/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_projects/components/import_projects_table_spec.js
index 419d67e239f..eb4f750ce41 100644
--- a/spec/frontend/import_projects/components/import_projects_table_spec.js
+++ b/spec/frontend/import_projects/components/import_projects_table_spec.js
@@ -2,17 +2,14 @@ import { nextTick } from 'vue';
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlButton } from '@gitlab/ui';
-import { state, getters } from '~/import_projects/store';
-import eventHub from '~/import_projects/event_hub';
+import state from '~/import_projects/store/state';
+import * as getters from '~/import_projects/store/getters';
+import { STATUSES } from '~/import_projects/constants';
import ImportProjectsTable from '~/import_projects/components/import_projects_table.vue';
import ImportedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue';
import ProviderRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue';
import IncompatibleRepoTableRow from '~/import_projects/components/incompatible_repo_table_row.vue';
-jest.mock('~/import_projects/event_hub', () => ({
- $emit: jest.fn(),
-}));
-
describe('ImportProjectsTable', () => {
let wrapper;
@@ -21,13 +18,6 @@ describe('ImportProjectsTable', () => {
const providerTitle = 'THE PROVIDER';
const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' };
- const importedProject = {
- id: 1,
- fullPath: 'fullPath',
- importStatus: 'started',
- providerLink: 'providerLink',
- importSource: 'importSource',
- };
const findImportAllButton = () =>
wrapper
@@ -35,6 +25,7 @@ describe('ImportProjectsTable', () => {
.filter(w => w.props().variant === 'success')
.at(0);
+ const importAllFn = jest.fn();
function createComponent({
state: initialState,
getters: customGetters,
@@ -52,8 +43,9 @@ describe('ImportProjectsTable', () => {
},
actions: {
fetchRepos: jest.fn(),
- fetchReposFiltered: jest.fn(),
fetchJobs: jest.fn(),
+ fetchNamespaces: jest.fn(),
+ importAll: importAllFn,
stopJobsPolling: jest.fn(),
clearJobsEtagPoll: jest.fn(),
setFilter: jest.fn(),
@@ -79,11 +71,13 @@ describe('ImportProjectsTable', () => {
});
it('renders a loading icon while repos are loading', () => {
- createComponent({
- state: {
- isLoadingRepos: true,
- },
- });
+ createComponent({ state: { isLoadingRepos: true } });
+
+ expect(wrapper.contains(GlLoadingIcon)).toBe(true);
+ });
+
+ it('renders a loading icon while namespaces are loading', () => {
+ createComponent({ state: { isLoadingNamespaces: true } });
expect(wrapper.contains(GlLoadingIcon)).toBe(true);
});
@@ -91,10 +85,16 @@ describe('ImportProjectsTable', () => {
it('renders a table with imported projects and provider repos', () => {
createComponent({
state: {
- importedProjects: [importedProject],
- providerRepos: [providerRepo],
- incompatibleRepos: [{ ...providerRepo, id: 11 }],
- namespaces: [{ path: 'path' }],
+ namespaces: [{ fullPath: 'path' }],
+ repositories: [
+ { importSource: { id: 1 }, importedProject: null, importStatus: STATUSES.NONE },
+ { importSource: { id: 2 }, importedProject: {}, importStatus: STATUSES.FINISHED },
+ {
+ importSource: { id: 3, incompatible: true },
+ importedProject: {},
+ importStatus: STATUSES.NONE,
+ },
+ ],
},
});
@@ -133,13 +133,7 @@ describe('ImportProjectsTable', () => {
);
it('renders an empty state if there are no projects available', () => {
- createComponent({
- state: {
- importedProjects: [],
- providerRepos: [],
- incompatibleProjects: [],
- },
- });
+ createComponent({ state: { repositories: [] } });
expect(wrapper.contains(ProviderRepoTableRow)).toBe(false);
expect(wrapper.contains(ImportedProjectTableRow)).toBe(false);
@@ -147,34 +141,29 @@ describe('ImportProjectsTable', () => {
});
it('sends importAll event when import button is clicked', async () => {
- createComponent({
- state: {
- providerRepos: [providerRepo],
- },
- });
+ createComponent({ state: { providerRepos: [providerRepo] } });
findImportAllButton().vm.$emit('click');
await nextTick();
- expect(eventHub.$emit).toHaveBeenCalledWith('importAll');
+
+ expect(importAllFn).toHaveBeenCalled();
});
it('shows loading spinner when import is in progress', () => {
- createComponent({
- getters: {
- isImportingAnyRepo: () => true,
- },
- });
+ createComponent({ getters: { isImportingAnyRepo: () => true } });
expect(findImportAllButton().props().loading).toBe(true);
});
it('renders filtering input field by default', () => {
createComponent();
+
expect(findFilterField().exists()).toBe(true);
});
it('does not render filtering input field when filterable is false', () => {
createComponent({ filterable: false });
+
expect(findFilterField().exists()).toBe(false);
});
diff --git a/spec/frontend/import_projects/components/imported_project_table_row_spec.js b/spec/frontend/import_projects/components/imported_project_table_row_spec.js
index 700dd1e025a..8890c352826 100644
--- a/spec/frontend/import_projects/components/imported_project_table_row_spec.js
+++ b/spec/frontend/import_projects/components/imported_project_table_row_spec.js
@@ -1,57 +1,44 @@
-import Vuex from 'vuex';
-import { createLocalVue, mount } from '@vue/test-utils';
-import createStore from '~/import_projects/store';
-import importedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue';
-import STATUS_MAP from '~/import_projects/constants';
+import { mount } from '@vue/test-utils';
+import ImportedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue';
+import ImportStatus from '~/import_projects/components/import_status.vue';
+import { STATUSES } from '~/import_projects/constants';
describe('ImportedProjectTableRow', () => {
- let vm;
+ let wrapper;
const project = {
- id: 1,
- fullPath: 'fullPath',
- importStatus: 'finished',
- providerLink: 'providerLink',
- importSource: 'importSource',
+ importSource: {
+ fullName: 'fullName',
+ providerLink: 'providerLink',
+ },
+ importedProject: {
+ id: 1,
+ fullPath: 'fullPath',
+ importSource: 'importSource',
+ },
+ importStatus: STATUSES.FINISHED,
};
function mountComponent() {
- const localVue = createLocalVue();
- localVue.use(Vuex);
-
- const component = mount(importedProjectTableRow, {
- localVue,
- store: createStore(),
- propsData: {
- project: {
- ...project,
- },
- },
- });
-
- return component.vm;
+ wrapper = mount(ImportedProjectTableRow, { propsData: { project } });
}
beforeEach(() => {
- vm = mountComponent();
+ mountComponent();
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
it('renders an imported project table row', () => {
- const providerLink = vm.$el.querySelector('.js-provider-link');
- const statusObject = STATUS_MAP[project.importStatus];
-
- expect(vm.$el.classList.contains('js-imported-project')).toBe(true);
- expect(providerLink.href).toMatch(project.providerLink);
- expect(providerLink.textContent).toMatch(project.importSource);
- expect(vm.$el.querySelector('.js-full-path').textContent).toMatch(project.fullPath);
- expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
- statusObject.text,
+ const providerLink = wrapper.find('[data-testid=providerLink]');
+
+ expect(providerLink.attributes().href).toMatch(project.importSource.providerLink);
+ expect(providerLink.text()).toMatch(project.importSource.fullName);
+ expect(wrapper.find('[data-testid=fullPath]').text()).toMatch(project.importedProject.fullPath);
+ expect(wrapper.find(ImportStatus).props().status).toBe(project.importStatus);
+ expect(wrapper.find('[data-testid=goToProject').attributes().href).toMatch(
+ project.importedProject.fullPath,
);
-
- expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
- expect(vm.$el.querySelector('.js-go-to-project').href).toMatch(project.fullPath);
});
});
diff --git a/spec/frontend/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_projects/components/provider_repo_table_row_spec.js
index f5e5141eac8..bd9cd07db78 100644
--- a/spec/frontend/import_projects/components/provider_repo_table_row_spec.js
+++ b/spec/frontend/import_projects/components/provider_repo_table_row_spec.js
@@ -1,100 +1,100 @@
+import { nextTick } from 'vue';
import Vuex from 'vuex';
-import { createLocalVue, mount } from '@vue/test-utils';
-import { state, actions, getters, mutations } from '~/import_projects/store';
-import providerRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue';
-import STATUS_MAP, { STATUSES } from '~/import_projects/constants';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import ProviderRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue';
+import ImportStatus from '~/import_projects/components/import_status.vue';
+import { STATUSES } from '~/import_projects/constants';
+import Select2Select from '~/vue_shared/components/select2_select.vue';
describe('ProviderRepoTableRow', () => {
- let vm;
+ let wrapper;
const fetchImport = jest.fn();
- const importPath = '/import-path';
- const defaultTargetNamespace = 'user';
- const ciCdOnly = true;
+ const setImportTarget = jest.fn();
+ const fakeImportTarget = {
+ targetNamespace: 'target',
+ newName: 'newName',
+ };
+ const ciCdOnly = false;
const repo = {
- id: 10,
- sanitizedName: 'sanitizedName',
- fullName: 'fullName',
- providerLink: 'providerLink',
+ importSource: {
+ id: 'remote-1',
+ fullName: 'fullName',
+ providerLink: 'providerLink',
+ },
+ importedProject: {
+ id: 1,
+ fullPath: 'fullPath',
+ importSource: 'importSource',
+ },
+ importStatus: STATUSES.FINISHED,
};
- function initStore(initialState) {
- const stubbedActions = { ...actions, fetchImport };
+ const availableNamespaces = [
+ { text: 'Groups', children: [{ id: 'test', text: 'test' }] },
+ { text: 'Users', children: [{ id: 'root', text: 'root' }] },
+ ];
+ function initStore(initialState) {
const store = new Vuex.Store({
- state: { ...state(), ...initialState },
- actions: stubbedActions,
- mutations,
- getters,
+ state: initialState,
+ getters: {
+ getImportTarget: () => () => fakeImportTarget,
+ },
+ actions: { fetchImport, setImportTarget },
});
return store;
}
+ const findImportButton = () =>
+ wrapper
+ .findAll('button')
+ .filter(node => node.text() === 'Import')
+ .at(0);
+
function mountComponent(initialState) {
const localVue = createLocalVue();
localVue.use(Vuex);
- const store = initStore({ importPath, defaultTargetNamespace, ciCdOnly, ...initialState });
+ const store = initStore({ ciCdOnly, ...initialState });
- const component = mount(providerRepoTableRow, {
+ wrapper = shallowMount(ProviderRepoTableRow, {
localVue,
store,
- propsData: {
- repo,
- },
+ propsData: { repo, availableNamespaces },
});
-
- return component.vm;
}
beforeEach(() => {
- vm = mountComponent();
+ mountComponent();
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
it('renders a provider repo table row', () => {
- const providerLink = vm.$el.querySelector('.js-provider-link');
- const statusObject = STATUS_MAP[STATUSES.NONE];
-
- expect(vm.$el.classList.contains('js-provider-repo')).toBe(true);
- expect(providerLink.href).toMatch(repo.providerLink);
- expect(providerLink.textContent).toMatch(repo.fullName);
- expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
- statusObject.text,
- );
-
- expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
- expect(vm.$el.querySelector('.js-import-button')).not.toBeNull();
+ const providerLink = wrapper.find('[data-testid=providerLink]');
+
+ expect(providerLink.attributes().href).toMatch(repo.importSource.providerLink);
+ expect(providerLink.text()).toMatch(repo.importSource.fullName);
+ expect(wrapper.find(ImportStatus).props().status).toBe(repo.importStatus);
+ expect(wrapper.contains('button')).toBe(true);
});
it('renders a select2 namespace select', () => {
- const dropdownTrigger = vm.$el.querySelector('.js-namespace-select');
-
- expect(dropdownTrigger).not.toBeNull();
- expect(dropdownTrigger.classList.contains('select2-container')).toBe(true);
-
- dropdownTrigger.click();
-
- expect(vm.$el.querySelector('.select2-drop')).not.toBeNull();
+ expect(wrapper.contains(Select2Select)).toBe(true);
+ expect(wrapper.find(Select2Select).props().options.data).toBe(availableNamespaces);
});
- it('imports repo when clicking import button', () => {
- vm.$el.querySelector('.js-import-button').click();
+ it('imports repo when clicking import button', async () => {
+ findImportButton().trigger('click');
- return vm.$nextTick().then(() => {
- const { calls } = fetchImport.mock;
+ await nextTick();
- // Not using .toBeCalledWith because it expects
- // an unmatchable and undefined 3rd argument.
- expect(calls.length).toBe(1);
- expect(calls[0][1]).toEqual({
- repo,
- newName: repo.sanitizedName,
- targetNamespace: defaultTargetNamespace,
- });
- });
+ const { calls } = fetchImport.mock;
+
+ expect(calls).toHaveLength(1);
+ expect(calls[0][1]).toBe(repo.importSource.id);
});
});
diff --git a/spec/frontend/import_projects/store/actions_spec.js b/spec/frontend/import_projects/store/actions_spec.js
index fd6fbcbfce0..fea9452e2f7 100644
--- a/spec/frontend/import_projects/store/actions_spec.js
+++ b/spec/frontend/import_projects/store/actions_spec.js
@@ -12,41 +12,76 @@ import {
RECEIVE_IMPORT_SUCCESS,
RECEIVE_IMPORT_ERROR,
RECEIVE_JOBS_SUCCESS,
+ REQUEST_NAMESPACES,
+ RECEIVE_NAMESPACES_SUCCESS,
+ RECEIVE_NAMESPACES_ERROR,
} from '~/import_projects/store/mutation_types';
-import {
- fetchRepos,
- fetchImport,
- receiveJobsSuccess,
- fetchJobs,
- clearJobsEtagPoll,
- stopJobsPolling,
-} from '~/import_projects/store/actions';
+import actionsFactory from '~/import_projects/store/actions';
+import { getImportTarget } from '~/import_projects/store/getters';
import state from '~/import_projects/store/state';
+import { STATUSES } from '~/import_projects/constants';
jest.mock('~/flash');
+const MOCK_ENDPOINT = `${TEST_HOST}/endpoint.json`;
+
+const {
+ clearJobsEtagPoll,
+ stopJobsPolling,
+ importAll,
+ fetchRepos,
+ fetchImport,
+ fetchJobs,
+ fetchNamespaces,
+} = actionsFactory({
+ endpoints: {
+ reposPath: MOCK_ENDPOINT,
+ importPath: MOCK_ENDPOINT,
+ jobsPath: MOCK_ENDPOINT,
+ namespacesPath: MOCK_ENDPOINT,
+ },
+});
+
describe('import_projects store actions', () => {
let localState;
- const repos = [{ id: 1 }, { id: 2 }];
- const importPayload = { newName: 'newName', targetNamespace: 'targetNamespace', repo: { id: 1 } };
+ const importRepoId = 1;
+ const otherImportRepoId = 2;
+ const defaultTargetNamespace = 'default';
+ const sanitizedName = 'sanitizedName';
+ const defaultImportTarget = { newName: sanitizedName, targetNamespace: defaultTargetNamespace };
beforeEach(() => {
- localState = state();
+ localState = {
+ ...state(),
+ defaultTargetNamespace,
+ repositories: [
+ { importSource: { id: importRepoId, sanitizedName }, importStatus: STATUSES.NONE },
+ {
+ importSource: { id: otherImportRepoId, sanitizedName: 's2' },
+ importStatus: STATUSES.NONE,
+ },
+ {
+ importSource: { id: 3, sanitizedName: 's3', incompatible: true },
+ importStatus: STATUSES.NONE,
+ },
+ ],
+ };
+
+ localState.getImportTarget = getImportTarget(localState);
});
describe('fetchRepos', () => {
let mock;
- const payload = { imported_projects: [{}], provider_repos: [{}], namespaces: [{}] };
+ const payload = { imported_projects: [{}], provider_repos: [{}] };
beforeEach(() => {
- localState.reposPath = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
});
afterEach(() => mock.restore());
it('dispatches stopJobsPolling actions and commits REQUEST_REPOS, RECEIVE_REPOS_SUCCESS mutations on a successful request', () => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, payload);
+ mock.onGet(MOCK_ENDPOINT).reply(200, payload);
return testAction(
fetchRepos,
@@ -64,7 +99,7 @@ describe('import_projects store actions', () => {
});
it('dispatches stopJobsPolling action and commits REQUEST_REPOS, RECEIVE_REPOS_ERROR mutations on an unsuccessful request', () => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
+ mock.onGet(MOCK_ENDPOINT).reply(500);
return testAction(
fetchRepos,
@@ -104,7 +139,6 @@ describe('import_projects store actions', () => {
let mock;
beforeEach(() => {
- localState.importPath = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
});
@@ -112,15 +146,17 @@ describe('import_projects store actions', () => {
it('commits REQUEST_IMPORT and REQUEST_IMPORT_SUCCESS mutations on a successful request', () => {
const importedProject = { name: 'imported/project' };
- const importRepoId = importPayload.repo.id;
- mock.onPost(`${TEST_HOST}/endpoint.json`).reply(200, importedProject);
+ mock.onPost(MOCK_ENDPOINT).reply(200, importedProject);
return testAction(
fetchImport,
- importPayload,
+ importRepoId,
localState,
[
- { type: REQUEST_IMPORT, payload: importRepoId },
+ {
+ type: REQUEST_IMPORT,
+ payload: { repoId: importRepoId, importTarget: defaultImportTarget },
+ },
{
type: RECEIVE_IMPORT_SUCCESS,
payload: {
@@ -134,15 +170,18 @@ describe('import_projects store actions', () => {
});
it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR and shows generic error message on an unsuccessful request', async () => {
- mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500);
+ mock.onPost(MOCK_ENDPOINT).reply(500);
await testAction(
fetchImport,
- importPayload,
+ importRepoId,
localState,
[
- { type: REQUEST_IMPORT, payload: importPayload.repo.id },
- { type: RECEIVE_IMPORT_ERROR, payload: importPayload.repo.id },
+ {
+ type: REQUEST_IMPORT,
+ payload: { repoId: importRepoId, importTarget: defaultImportTarget },
+ },
+ { type: RECEIVE_IMPORT_ERROR, payload: importRepoId },
],
[],
);
@@ -152,15 +191,18 @@ describe('import_projects store actions', () => {
it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR and shows detailed error message on an unsuccessful request with errors fields in response', async () => {
const ERROR_MESSAGE = 'dummy';
- mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500, { errors: ERROR_MESSAGE });
+ mock.onPost(MOCK_ENDPOINT).reply(500, { errors: ERROR_MESSAGE });
await testAction(
fetchImport,
- importPayload,
+ importRepoId,
localState,
[
- { type: REQUEST_IMPORT, payload: importPayload.repo.id },
- { type: RECEIVE_IMPORT_ERROR, payload: importPayload.repo.id },
+ {
+ type: REQUEST_IMPORT,
+ payload: { repoId: importRepoId, importTarget: defaultImportTarget },
+ },
+ { type: RECEIVE_IMPORT_ERROR, payload: importRepoId },
],
[],
);
@@ -169,24 +211,11 @@ describe('import_projects store actions', () => {
});
});
- describe('receiveJobsSuccess', () => {
- it(`commits ${RECEIVE_JOBS_SUCCESS} mutation`, () => {
- return testAction(
- receiveJobsSuccess,
- repos,
- localState,
- [{ type: RECEIVE_JOBS_SUCCESS, payload: repos }],
- [],
- );
- });
- });
-
describe('fetchJobs', () => {
let mock;
const updatedProjects = [{ name: 'imported/project' }, { name: 'provider/repo' }];
beforeEach(() => {
- localState.jobsPath = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
});
@@ -198,7 +227,7 @@ describe('import_projects store actions', () => {
afterEach(() => mock.restore());
it('commits RECEIVE_JOBS_SUCCESS mutation on a successful request', async () => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, updatedProjects);
+ mock.onGet(MOCK_ENDPOINT).reply(200, updatedProjects);
await testAction(
fetchJobs,
@@ -237,4 +266,62 @@ describe('import_projects store actions', () => {
});
});
});
+
+ describe('fetchNamespaces', () => {
+ let mock;
+ const namespaces = [{ full_name: 'test/ns1' }, { full_name: 'test_ns2' }];
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => mock.restore());
+
+ it('commits REQUEST_NAMESPACES and RECEIVE_NAMESPACES_SUCCESS on success', async () => {
+ mock.onGet(MOCK_ENDPOINT).reply(200, namespaces);
+
+ await testAction(
+ fetchNamespaces,
+ null,
+ localState,
+ [
+ { type: REQUEST_NAMESPACES },
+ {
+ type: RECEIVE_NAMESPACES_SUCCESS,
+ payload: convertObjectPropsToCamelCase(namespaces, { deep: true }),
+ },
+ ],
+ [],
+ );
+ });
+
+ it('commits REQUEST_NAMESPACES and RECEIVE_NAMESPACES_ERROR and shows generic error message on an unsuccessful request', async () => {
+ mock.onGet(MOCK_ENDPOINT).reply(500);
+
+ await testAction(
+ fetchNamespaces,
+ null,
+ localState,
+ [{ type: REQUEST_NAMESPACES }, { type: RECEIVE_NAMESPACES_ERROR }],
+ [],
+ );
+
+ expect(createFlash).toHaveBeenCalledWith('Requesting namespaces failed');
+ });
+ });
+
+ describe('importAll', () => {
+ it('dispatches multiple fetchImport actions', async () => {
+ await testAction(
+ importAll,
+ null,
+ localState,
+ [],
+ [
+ { type: 'fetchImport', payload: importRepoId },
+ { type: 'fetchImport', payload: otherImportRepoId },
+ ],
+ );
+ });
+ });
});
diff --git a/spec/frontend/import_projects/store/getters_spec.js b/spec/frontend/import_projects/store/getters_spec.js
index 93d1ed89783..5c1ea25a684 100644
--- a/spec/frontend/import_projects/store/getters_spec.js
+++ b/spec/frontend/import_projects/store/getters_spec.js
@@ -1,12 +1,28 @@
import {
- namespaceSelectOptions,
+ isLoading,
isImportingAnyRepo,
- hasProviderRepos,
hasIncompatibleRepos,
- hasImportedProjects,
+ hasImportableRepos,
+ getImportTarget,
} from '~/import_projects/store/getters';
+import { STATUSES } from '~/import_projects/constants';
import state from '~/import_projects/store/state';
+const IMPORTED_REPO = {
+ importSource: {},
+ importedProject: { fullPath: 'some/path' },
+};
+
+const IMPORTABLE_REPO = {
+ importSource: { id: 'some-id', sanitizedName: 'sanitized' },
+ importedProject: null,
+ importStatus: STATUSES.NONE,
+};
+
+const INCOMPATIBLE_REPO = {
+ importSource: { incompatible: true },
+};
+
describe('import_projects store getters', () => {
let localState;
@@ -14,85 +30,87 @@ describe('import_projects store getters', () => {
localState = state();
});
- describe('namespaceSelectOptions', () => {
- const namespaces = [{ fullPath: 'namespace-0' }, { fullPath: 'namespace-1' }];
- const defaultTargetNamespace = 'current-user';
-
- it('returns an options array with a "Users" and "Groups" optgroups', () => {
- localState.namespaces = namespaces;
- localState.defaultTargetNamespace = defaultTargetNamespace;
-
- const optionsArray = namespaceSelectOptions(localState);
- const groupsGroup = optionsArray[0];
- const usersGroup = optionsArray[1];
-
- expect(groupsGroup.text).toBe('Groups');
- expect(usersGroup.text).toBe('Users');
-
- groupsGroup.children.forEach((child, index) => {
- expect(child.id).toBe(namespaces[index].fullPath);
- expect(child.text).toBe(namespaces[index].fullPath);
+ it.each`
+ isLoadingRepos | isLoadingNamespaces | isLoadingValue
+ ${false} | ${false} | ${false}
+ ${true} | ${false} | ${true}
+ ${false} | ${true} | ${true}
+ ${true} | ${true} | ${true}
+ `(
+ 'isLoading returns $isLoadingValue when isLoadingRepos is $isLoadingRepos and isLoadingNamespaces is $isLoadingNamespaces',
+ ({ isLoadingRepos, isLoadingNamespaces, isLoadingValue }) => {
+ Object.assign(localState, {
+ isLoadingRepos,
+ isLoadingNamespaces,
});
- expect(usersGroup.children.length).toBe(1);
- expect(usersGroup.children[0].id).toBe(defaultTargetNamespace);
- expect(usersGroup.children[0].text).toBe(defaultTargetNamespace);
- });
- });
-
- describe('isImportingAnyRepo', () => {
- it('returns true if there are any reposBeingImported', () => {
- localState.reposBeingImported = new Array(1);
-
- expect(isImportingAnyRepo(localState)).toBe(true);
- });
+ expect(isLoading(localState)).toBe(isLoadingValue);
+ },
+ );
+
+ it.each`
+ importStatus | value
+ ${STATUSES.NONE} | ${false}
+ ${STATUSES.SCHEDULING} | ${true}
+ ${STATUSES.SCHEDULED} | ${true}
+ ${STATUSES.STARTED} | ${true}
+ ${STATUSES.FINISHED} | ${false}
+ `(
+ 'isImportingAnyRepo returns $value when repo with $importStatus status is available',
+ ({ importStatus, value }) => {
+ localState.repositories = [{ importStatus }];
+
+ expect(isImportingAnyRepo(localState)).toBe(value);
+ },
+ );
- it('returns false if there are no reposBeingImported', () => {
- localState.reposBeingImported = [];
-
- expect(isImportingAnyRepo(localState)).toBe(false);
- });
- });
-
- describe('hasProviderRepos', () => {
- it('returns true if there are any providerRepos', () => {
- localState.providerRepos = new Array(1);
+ describe('hasIncompatibleRepos', () => {
+ it('returns true if there are any incompatible projects', () => {
+ localState.repositories = [IMPORTABLE_REPO, IMPORTED_REPO, INCOMPATIBLE_REPO];
- expect(hasProviderRepos(localState)).toBe(true);
+ expect(hasIncompatibleRepos(localState)).toBe(true);
});
- it('returns false if there are no providerRepos', () => {
- localState.providerRepos = [];
+ it('returns false if there are no incompatible projects', () => {
+ localState.repositories = [IMPORTABLE_REPO, IMPORTED_REPO];
- expect(hasProviderRepos(localState)).toBe(false);
+ expect(hasIncompatibleRepos(localState)).toBe(false);
});
});
- describe('hasImportedProjects', () => {
- it('returns true if there are any importedProjects', () => {
- localState.importedProjects = new Array(1);
+ describe('hasImportableRepos', () => {
+ it('returns true if there are any importable projects ', () => {
+ localState.repositories = [IMPORTABLE_REPO, IMPORTED_REPO, INCOMPATIBLE_REPO];
- expect(hasImportedProjects(localState)).toBe(true);
+ expect(hasImportableRepos(localState)).toBe(true);
});
- it('returns false if there are no importedProjects', () => {
- localState.importedProjects = [];
+ it('returns false if there are no importable projects', () => {
+ localState.repositories = [IMPORTED_REPO, INCOMPATIBLE_REPO];
- expect(hasImportedProjects(localState)).toBe(false);
+ expect(hasImportableRepos(localState)).toBe(false);
});
});
- describe('hasIncompatibleRepos', () => {
- it('returns true if there are any incompatibleProjects', () => {
- localState.incompatibleRepos = new Array(1);
+ describe('getImportTarget', () => {
+ it('returns default value if no custom target available', () => {
+ localState.defaultTargetNamespace = 'default';
+ localState.repositories = [IMPORTABLE_REPO];
- expect(hasIncompatibleRepos(localState)).toBe(true);
+ expect(getImportTarget(localState)(IMPORTABLE_REPO.importSource.id)).toStrictEqual({
+ newName: IMPORTABLE_REPO.importSource.sanitizedName,
+ targetNamespace: localState.defaultTargetNamespace,
+ });
});
- it('returns false if there are no incompatibleProjects', () => {
- localState.incompatibleRepos = [];
+ it('returns custom import target if available', () => {
+ const fakeTarget = { newName: 'something', targetNamespace: 'ns' };
+ localState.repositories = [IMPORTABLE_REPO];
+ localState.customImportTargets[IMPORTABLE_REPO.importSource.id] = fakeTarget;
- expect(hasIncompatibleRepos(localState)).toBe(false);
+ expect(getImportTarget(localState)(IMPORTABLE_REPO.importSource.id)).toStrictEqual(
+ fakeTarget,
+ );
});
});
});
diff --git a/spec/frontend/import_projects/store/mutations_spec.js b/spec/frontend/import_projects/store/mutations_spec.js
index 505545f7aa5..fb700845cd6 100644
--- a/spec/frontend/import_projects/store/mutations_spec.js
+++ b/spec/frontend/import_projects/store/mutations_spec.js
@@ -1,34 +1,282 @@
import * as types from '~/import_projects/store/mutation_types';
import mutations from '~/import_projects/store/mutations';
+import { STATUSES } from '~/import_projects/constants';
describe('import_projects store mutations', () => {
- describe(`${types.RECEIVE_IMPORT_SUCCESS}`, () => {
- it('removes repoId from reposBeingImported and providerRepos, adds to importedProjects', () => {
- const repoId = 1;
- const state = {
- reposBeingImported: [repoId],
- providerRepos: [{ id: repoId }],
+ let state;
+ const SOURCE_PROJECT = {
+ id: 1,
+ full_name: 'full/name',
+ sanitized_name: 'name',
+ provider_link: 'https://demo.link/full/name',
+ };
+ const IMPORTED_PROJECT = {
+ name: 'demo',
+ importSource: 'something',
+ providerLink: 'custom-link',
+ importStatus: 'status',
+ fullName: 'fullName',
+ };
+
+ describe(`${types.SET_FILTER}`, () => {
+ it('overwrites current filter value', () => {
+ state = { filter: 'some-value' };
+ const NEW_VALUE = 'new-value';
+
+ mutations[types.SET_FILTER](state, NEW_VALUE);
+
+ expect(state.filter).toBe(NEW_VALUE);
+ });
+ });
+
+ describe(`${types.REQUEST_REPOS}`, () => {
+ it('sets repos loading flag to true', () => {
+ state = {};
+
+ mutations[types.REQUEST_REPOS](state);
+
+ expect(state.isLoadingRepos).toBe(true);
+ });
+ });
+
+ describe(`${types.RECEIVE_REPOS_SUCCESS}`, () => {
+ describe('for imported projects', () => {
+ const response = {
+ importedProjects: [IMPORTED_PROJECT],
+ providerRepos: [],
+ };
+
+ it('picks import status from response', () => {
+ state = {};
+
+ mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
+
+ expect(state.repositories[0].importStatus).toBe(IMPORTED_PROJECT.importStatus);
+ });
+
+ it('recreates importSource from response', () => {
+ state = {};
+
+ mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
+
+ expect(state.repositories[0].importSource).toStrictEqual(
+ expect.objectContaining({
+ fullName: IMPORTED_PROJECT.importSource,
+ sanitizedName: IMPORTED_PROJECT.name,
+ providerLink: IMPORTED_PROJECT.providerLink,
+ }),
+ );
+ });
+
+ it('passes project to importProject', () => {
+ state = {};
+
+ mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
+
+ expect(IMPORTED_PROJECT).toStrictEqual(
+ expect.objectContaining(state.repositories[0].importedProject),
+ );
+ });
+ });
+
+ describe('for importable projects', () => {
+ beforeEach(() => {
+ state = {};
+ const response = {
+ importedProjects: [],
+ providerRepos: [SOURCE_PROJECT],
+ };
+ mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
+ });
+
+ it('sets import status to none', () => {
+ expect(state.repositories[0].importStatus).toBe(STATUSES.NONE);
+ });
+
+ it('sets importSource to project', () => {
+ expect(state.repositories[0].importSource).toBe(SOURCE_PROJECT);
+ });
+ });
+
+ describe('for incompatible projects', () => {
+ const response = {
importedProjects: [],
+ providerRepos: [],
+ incompatibleRepos: [SOURCE_PROJECT],
};
- const importedProject = { id: repoId };
- mutations[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId });
+ beforeEach(() => {
+ state = {};
+ mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
+ });
- expect(state.reposBeingImported.includes(repoId)).toBe(false);
- expect(state.providerRepos.some(repo => repo.id === repoId)).toBe(false);
- expect(state.importedProjects.some(repo => repo.id === repoId)).toBe(true);
+ it('sets incompatible flag', () => {
+ expect(state.repositories[0].importSource.incompatible).toBe(true);
+ });
+
+ it('sets importSource to project', () => {
+ expect(state.repositories[0].importSource).toStrictEqual(
+ expect.objectContaining(SOURCE_PROJECT),
+ );
+ });
+ });
+
+ it('sets repos loading flag to false', () => {
+ const response = {
+ importedProjects: [],
+ providerRepos: [],
+ };
+ state = {};
+
+ mutations[types.RECEIVE_REPOS_SUCCESS](state, response);
+
+ expect(state.isLoadingRepos).toBe(false);
+ });
+ });
+
+ describe(`${types.RECEIVE_REPOS_ERROR}`, () => {
+ it('sets repos loading flag to false', () => {
+ state = {};
+
+ mutations[types.RECEIVE_REPOS_ERROR](state);
+
+ expect(state.isLoadingRepos).toBe(false);
+ });
+ });
+
+ describe(`${types.REQUEST_IMPORT}`, () => {
+ beforeEach(() => {
+ const REPO_ID = 1;
+ const importTarget = { targetNamespace: 'ns', newName: 'name ' };
+ state = { repositories: [{ importSource: { id: REPO_ID } }] };
+
+ mutations[types.REQUEST_IMPORT](state, { repoId: REPO_ID, importTarget });
+ });
+
+ it(`sets status to ${STATUSES.SCHEDULING}`, () => {
+ expect(state.repositories[0].importStatus).toBe(STATUSES.SCHEDULING);
+ });
+ });
+
+ describe(`${types.RECEIVE_IMPORT_SUCCESS}`, () => {
+ beforeEach(() => {
+ const REPO_ID = 1;
+ state = { repositories: [{ importSource: { id: REPO_ID } }] };
+
+ mutations[types.RECEIVE_IMPORT_SUCCESS](state, {
+ repoId: REPO_ID,
+ importedProject: IMPORTED_PROJECT,
+ });
+ });
+
+ it('sets import status', () => {
+ expect(state.repositories[0].importStatus).toBe(IMPORTED_PROJECT.importStatus);
+ });
+
+ it('sets imported project', () => {
+ expect(IMPORTED_PROJECT).toStrictEqual(
+ expect.objectContaining(state.repositories[0].importedProject),
+ );
+ });
+ });
+
+ describe(`${types.RECEIVE_IMPORT_ERROR}`, () => {
+ beforeEach(() => {
+ const REPO_ID = 1;
+ state = { repositories: [{ importSource: { id: REPO_ID } }] };
+
+ mutations[types.RECEIVE_IMPORT_ERROR](state, REPO_ID);
+ });
+
+ it(`resets import status to ${STATUSES.NONE}`, () => {
+ expect(state.repositories[0].importStatus).toBe(STATUSES.NONE);
});
});
describe(`${types.RECEIVE_JOBS_SUCCESS}`, () => {
- it('updates importStatus of existing importedProjects', () => {
+ it('updates import status of existing project', () => {
const repoId = 1;
- const state = { importedProjects: [{ id: repoId, importStatus: 'started' }] };
- const updatedProjects = [{ id: repoId, importStatus: 'finished' }];
+ state = {
+ repositories: [{ importedProject: { id: repoId }, importStatus: STATUSES.STARTED }],
+ };
+ const updatedProjects = [{ id: repoId, importStatus: STATUSES.FINISHED }];
mutations[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects);
- expect(state.importedProjects[0].importStatus).toBe(updatedProjects[0].importStatus);
+ expect(state.repositories[0].importStatus).toBe(updatedProjects[0].importStatus);
+ });
+ });
+
+ describe(`${types.REQUEST_NAMESPACES}`, () => {
+ it('sets namespaces loading flag to true', () => {
+ state = {};
+
+ mutations[types.REQUEST_NAMESPACES](state);
+
+ expect(state.isLoadingNamespaces).toBe(true);
+ });
+ });
+
+ describe(`${types.RECEIVE_NAMESPACES_SUCCESS}`, () => {
+ const response = [{ fullPath: 'some/path' }];
+
+ beforeEach(() => {
+ state = {};
+ mutations[types.RECEIVE_NAMESPACES_SUCCESS](state, response);
+ });
+
+ it('stores namespaces to state', () => {
+ expect(state.namespaces).toStrictEqual(response);
+ });
+
+ it('sets namespaces loading flag to false', () => {
+ expect(state.isLoadingNamespaces).toBe(false);
+ });
+ });
+
+ describe(`${types.RECEIVE_NAMESPACES_ERROR}`, () => {
+ it('sets namespaces loading flag to false', () => {
+ state = {};
+
+ mutations[types.RECEIVE_NAMESPACES_ERROR](state);
+
+ expect(state.isLoadingNamespaces).toBe(false);
+ });
+ });
+
+ describe(`${types.SET_IMPORT_TARGET}`, () => {
+ const PROJECT = {
+ id: 2,
+ sanitizedName: 'sanitizedName',
+ };
+
+ it('stores custom target if it differs from defaults', () => {
+ state = { customImportTargets: {}, repositories: [{ importSource: PROJECT }] };
+ const importTarget = { targetNamespace: 'ns', newName: 'name ' };
+
+ mutations[types.SET_IMPORT_TARGET](state, { repoId: PROJECT.id, importTarget });
+ expect(state.customImportTargets[PROJECT.id]).toBe(importTarget);
+ });
+
+ it('removes custom target if it is equal to defaults', () => {
+ const importTarget = { targetNamespace: 'ns', newName: 'name ' };
+ state = {
+ defaultTargetNamespace: 'default',
+ customImportTargets: {
+ [PROJECT.id]: importTarget,
+ },
+ repositories: [{ importSource: PROJECT }],
+ };
+
+ mutations[types.SET_IMPORT_TARGET](state, {
+ repoId: PROJECT.id,
+ importTarget: {
+ targetNamespace: state.defaultTargetNamespace,
+ newName: PROJECT.sanitizedName,
+ },
+ });
+
+ expect(state.customImportTargets[SOURCE_PROJECT.id]).toBeUndefined();
});
});
});
diff --git a/spec/frontend/import_projects/utils_spec.js b/spec/frontend/import_projects/utils_spec.js
new file mode 100644
index 00000000000..826b06d5a70
--- /dev/null
+++ b/spec/frontend/import_projects/utils_spec.js
@@ -0,0 +1,32 @@
+import { isProjectImportable } from '~/import_projects/utils';
+import { STATUSES } from '~/import_projects/constants';
+
+describe('import_projects utils', () => {
+ describe('isProjectImportable', () => {
+ it.each`
+ status | result
+ ${STATUSES.FINISHED} | ${false}
+ ${STATUSES.FAILED} | ${false}
+ ${STATUSES.SCHEDULED} | ${false}
+ ${STATUSES.STARTED} | ${false}
+ ${STATUSES.NONE} | ${true}
+ ${STATUSES.SCHEDULING} | ${false}
+ `('returns $result when project is compatible and status is $status', ({ status, result }) => {
+ expect(
+ isProjectImportable({
+ importStatus: status,
+ importSource: { incompatible: false },
+ }),
+ ).toBe(result);
+ });
+
+ it('returns false if project is not compatible', () => {
+ expect(
+ isProjectImportable({
+ importStatus: STATUSES.NONE,
+ importSource: { incompatible: true },
+ }),
+ ).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/incidents/components/incidents_list_spec.js b/spec/frontend/incidents/components/incidents_list_spec.js
index aabafaa9154..34dc61772d4 100644
--- a/spec/frontend/incidents/components/incidents_list_spec.js
+++ b/spec/frontend/incidents/components/incidents_list_spec.js
@@ -10,7 +10,7 @@ import {
GlTabs,
GlBadge,
} from '@gitlab/ui';
-import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
+import { visitUrl, joinPaths, mergeUrlParams } from '~/lib/utils/url_utility';
import IncidentsList from '~/incidents/components/incidents_list.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { I18N, INCIDENT_STATUS_TABS } from '~/incidents/constants';
@@ -26,6 +26,7 @@ describe('Incidents List', () => {
let wrapper;
const newIssuePath = 'namespace/project/-/issues/new';
const incidentTemplateName = 'incident';
+ const incidentType = 'incident';
const incidentsCount = {
opened: 14,
closed: 1,
@@ -66,6 +67,7 @@ describe('Incidents List', () => {
projectPath: '/project/path',
newIssuePath,
incidentTemplateName,
+ incidentType,
issuePath: '/project/isssues',
publishedAvailable: true,
},
@@ -167,8 +169,13 @@ describe('Incidents List', () => {
});
});
- it('shows the button linking to new incidents page with prefilled incident template', () => {
+ it('shows the button linking to new incidents page with prefilled incident template when clicked', () => {
expect(findCreateIncidentBtn().exists()).toBe(true);
+ findCreateIncidentBtn().trigger('click');
+ expect(mergeUrlParams).toHaveBeenCalledWith(
+ { issuable_template: incidentTemplateName, 'issue[issue_type]': incidentType },
+ newIssuePath,
+ );
});
it('sets button loading on click', () => {
diff --git a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
index 46fcb92455b..691e19473c1 100644
--- a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
+++ b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
@@ -1,16 +1,19 @@
import { shallowMount } from '@vue/test-utils';
-import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
describe('Time ago with tooltip component', () => {
let vm;
- const buildVm = (propsData = {}) => {
+ const buildVm = (propsData = {}, scopedSlots = {}) => {
vm = shallowMount(TimeAgoTooltip, {
propsData,
+ scopedSlots,
});
};
const timestamp = '2017-05-08T14:57:39.781Z';
+ const timeAgoTimestamp = getTimeago().format(timestamp);
afterEach(() => {
vm.destroy();
@@ -20,10 +23,9 @@ describe('Time ago with tooltip component', () => {
buildVm({
time: timestamp,
});
- const timeago = getTimeago();
expect(vm.attributes('title')).toEqual(formatDate(timestamp));
- expect(vm.text()).toEqual(timeago.format(timestamp));
+ expect(vm.text()).toEqual(timeAgoTimestamp);
});
it('should render provided html class', () => {
@@ -34,4 +36,16 @@ describe('Time ago with tooltip component', () => {
expect(vm.classes()).toContain('foo');
});
+
+ it('should render with the datetime attribute', () => {
+ buildVm({ time: timestamp });
+
+ expect(vm.attributes('datetime')).toEqual(timestamp);
+ });
+
+ it('should render provided scope content with the correct timeAgo string', () => {
+ buildVm({ time: timestamp }, { default: `<span>The time is {{ props.timeAgo }}</span>` });
+
+ expect(vm.text()).toEqual(`The time is ${timeAgoTimestamp}`);
+ });
});
diff --git a/spec/helpers/projects/incidents_helper_spec.rb b/spec/helpers/projects/incidents_helper_spec.rb
index 8f908b532c0..8c9e2c0108e 100644
--- a/spec/helpers/projects/incidents_helper_spec.rb
+++ b/spec/helpers/projects/incidents_helper_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe Projects::IncidentsHelper do
'project-path' => project_path,
'new-issue-path' => new_issue_path,
'incident-template-name' => 'incident',
+ 'incident-type' => 'incident',
'issue-path' => issue_path
)
end
diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb
index 9c4dfcbfd54..4a9508712a4 100644
--- a/spec/lib/gitlab/badge/coverage/report_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/report_spec.rb
@@ -102,7 +102,7 @@ RSpec.describe Gitlab::Badge::Coverage::Report do
create(:ci_pipeline, opts).tap do |pipeline|
yield pipeline
- pipeline.update_legacy_status
+ ::Ci::ProcessPipelineService.new(pipeline).execute
end
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index cab32e17630..28626cdf13a 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -2096,58 +2096,6 @@ RSpec.describe Ci::Pipeline, :mailer do
end
end
- describe '#update_status' do
- context 'when pipeline is empty' do
- it 'updates does not change pipeline status' do
- expect(pipeline.statuses.latest.slow_composite_status(project: project)).to be_nil
-
- expect { pipeline.update_legacy_status }
- .to change { pipeline.reload.status }
- .from('created')
- .to('skipped')
- end
- end
-
- context 'when updating status to pending' do
- before do
- create(:ci_build, pipeline: pipeline, status: :running)
- end
-
- it 'updates pipeline status to running' do
- expect { pipeline.update_legacy_status }
- .to change { pipeline.reload.status }
- .from('created')
- .to('running')
- end
- end
-
- context 'when updating status to scheduled' do
- before do
- create(:ci_build, pipeline: pipeline, status: :scheduled)
- end
-
- it 'updates pipeline status to scheduled' do
- expect { pipeline.update_legacy_status }
- .to change { pipeline.reload.status }
- .from('created')
- .to('scheduled')
- end
- end
-
- context 'when statuses status was not recognized' do
- before do
- allow(pipeline)
- .to receive(:latest_builds_status)
- .and_return(:unknown)
- end
-
- it 'raises an exception' do
- expect { pipeline.update_legacy_status }
- .to raise_error(Ci::HasStatus::UnknownStatusError)
- end
- end
- end
-
describe '#detailed_status' do
subject { pipeline.detailed_status(user) }
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index d5992e34800..0f765d6b09b 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -118,6 +118,46 @@ RSpec.describe NotificationSetting do
expect(subject.event_enabled?(:foo_event)).to be(false)
end
end
+
+ describe 'for failed_pipeline' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:column, :expected) do
+ nil | true
+ true | true
+ false | false
+ end
+
+ with_them do
+ before do
+ subject.update!(failed_pipeline: column)
+ end
+
+ it do
+ expect(subject.event_enabled?(:failed_pipeline)).to eq(expected)
+ end
+ end
+ end
+
+ describe 'for fixed_pipeline' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:column, :expected) do
+ nil | true
+ true | true
+ false | false
+ end
+
+ with_them do
+ before do
+ subject.update!(fixed_pipeline: column)
+ end
+
+ it do
+ expect(subject.event_enabled?(:fixed_pipeline)).to eq(expected)
+ end
+ end
+ end
end
describe '.email_events' do
diff --git a/spec/serializers/diffs_metadata_entity_spec.rb b/spec/serializers/diffs_metadata_entity_spec.rb
index 8ed47569b75..e8cbc2076d7 100644
--- a/spec/serializers/diffs_metadata_entity_spec.rb
+++ b/spec/serializers/diffs_metadata_entity_spec.rb
@@ -30,6 +30,7 @@ RSpec.describe DiffsMetadataEntity do
:email_patch_path, :plain_diff_path,
:merge_request_diffs, :context_commits,
:definition_path_prefix, :source_branch_exists,
+ :can_merge, :conflict_resolution_path, :has_conflicts,
# Attributes
:diff_files
)
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index fa46d6c4d1d..212c8f99865 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -364,7 +364,7 @@ RSpec.describe Ci::RetryPipelineService, '#execute' do
stage: "stage_#{stage_num}",
stage_idx: stage_num,
pipeline: pipeline, **opts) do |build|
- pipeline.update_legacy_status
+ ::Ci::ProcessPipelineService.new(pipeline).execute
end
end
end
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 68b226b02da..93eef8a2732 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -147,5 +147,19 @@ RSpec.describe Issues::BuildService do
expect(issue.milestone).to be_nil
end
+
+ context 'setting issue type' do
+ it 'sets the issue_type on the issue' do
+ issue = build_issue(issue_type: 'incident')
+
+ expect(issue.issue_type).to eq('incident')
+ end
+
+ it 'defaults to issue if issue_type not given' do
+ issue = build_issue
+
+ expect(issue.issue_type).to eq('issue')
+ end
+ end
end
end