summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md9
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock15
-rw-r--r--app/assets/images/new_nav.pngbin0 -> 23771 bytes
-rw-r--r--app/assets/images/old_nav.pngbin0 -> 25617 bytes
-rw-r--r--app/assets/javascripts/dispatcher.js4
-rw-r--r--app/assets/javascripts/experimental_flags.js11
-rw-r--r--app/assets/javascripts/locale/eo/app.js1
-rw-r--r--app/assets/javascripts/main.js3
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue3
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.js18
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js9
-rw-r--r--app/assets/stylesheets/framework/header.scss17
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/new_nav.scss267
-rw-r--r--app/assets/stylesheets/pages/issuable.scss10
-rw-r--r--app/helpers/application_helper.rb8
-rw-r--r--app/helpers/button_helper.rb11
-rw-r--r--app/helpers/labels_helper.rb15
-rw-r--r--app/helpers/projects_helper.rb17
-rw-r--r--app/models/merge_request.rb9
-rw-r--r--app/models/project_services/prometheus_service.rb2
-rw-r--r--app/models/user.rb8
-rw-r--r--app/views/admin/runners/index.html.haml35
-rw-r--r--app/views/layouts/_head.html.haml3
-rw-r--r--app/views/layouts/application.html.haml5
-rw-r--r--app/views/layouts/header/_default.html.haml3
-rw-r--r--app/views/layouts/header/_new.html.haml93
-rw-r--r--app/views/layouts/header/_new_dropdown.haml8
-rw-r--r--app/views/layouts/nav/_new_dashboard.html.haml33
-rw-r--r--app/views/layouts/nav/_new_explore.html.haml19
-rw-r--r--app/views/profiles/preferences/show.html.haml19
-rw-r--r--app/views/projects/deployments/_deployment.html.haml11
-rw-r--r--app/views/projects/environments/show.html.haml4
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml2
-rw-r--r--app/views/shared/_label.html.haml39
-rw-r--r--app/views/shared/_no_password.html.haml7
-rw-r--r--app/views/shared/_no_ssh.html.haml4
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml4
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml5
-rw-r--r--app/views/shared/milestones/_milestone.html.haml2
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml4
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml4
-rw-r--r--changelogs/unreleased/33846-no-runner-for-admin.yml4
-rw-r--r--changelogs/unreleased/34010-fix-linking-to-parallel-diff-line-number-creating-gray-box.yml4
-rw-r--r--changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml4
-rw-r--r--changelogs/unreleased/34282-fix-api-using-include_missing-false.yml4
-rw-r--r--changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml4
-rw-r--r--changelogs/unreleased/34309-drop-gfm-mr-ms.yml4
-rw-r--r--changelogs/unreleased/dm-requirements-txt-tilde.yml5
-rw-r--r--changelogs/unreleased/fix-33991.yml4
-rw-r--r--changelogs/unreleased/fix-34019.yml4
-rw-r--r--changelogs/unreleased/issue-inline-edit-quick-submit.yml4
-rw-r--r--changelogs/unreleased/mk-fix-breadcrumb-order-33938.yml4
-rw-r--r--changelogs/unreleased/mk-fix-issue-34068.yml4
-rw-r--r--changelogs/unreleased/pat-alert-when-signin-disabled.yml4
-rw-r--r--changelogs/unreleased/polish-sidebar-toggle.yml4
-rw-r--r--changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml5
-rw-r--r--changelogs/unreleased/update_bootsnap_1-1-1.yml4
-rw-r--r--config/application.rb1
-rw-r--r--config/boot.rb6
-rw-r--r--db/migrate/20170622162730_add_ref_fetched_to_merge_request.rb9
-rw-r--r--db/schema.rb3
-rw-r--r--doc/user/project/integrations/webhooks.md2
-rw-r--r--features/dashboard/merge_requests.feature21
-rw-r--r--features/dashboard/todos.feature28
-rw-r--r--features/steps/dashboard/merge_requests.rb121
-rw-r--r--features/steps/dashboard/todos.rb191
-rw-r--r--features/steps/shared/paths.rb4
-rw-r--r--lib/api/variables.rb2
-rw-r--r--lib/gitlab/ee_compat_check.rb21
-rw-r--r--lib/gitlab/exclusive_lease.rb19
-rw-r--r--lib/gitlab/git/commit.rb4
-rw-r--r--lib/gitlab/i18n.rb3
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--locale/eo/gitlab.po1143
-rw-r--r--locale/eo/gitlab.po.time_stamp0
-rw-r--r--spec/features/admin/admin_runners_spec.rb61
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb97
-rw-r--r--spec/features/dashboard/milestone_filter_spec.rb22
-rw-r--r--spec/features/dashboard/todos/target_state_spec.rb (renamed from spec/features/todos/target_state_spec.rb)10
-rw-r--r--spec/features/dashboard/todos/todos_filtering_spec.rb (renamed from spec/features/todos/todos_filtering_spec.rb)14
-rw-r--r--spec/features/dashboard/todos/todos_sorting_spec.rb (renamed from spec/features/todos/todos_sorting_spec.rb)56
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb355
-rw-r--r--spec/features/groups/labels/subscription_spec.rb51
-rw-r--r--spec/features/login_spec.rb23
-rw-r--r--spec/features/projects/no_password_spec.rb69
-rw-r--r--spec/features/todos/todos_spec.rb355
-rw-r--r--spec/helpers/button_helper_spec.rb65
-rw-r--r--spec/helpers/groups_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb76
-rw-r--r--spec/javascripts/pipelines/stage_spec.js43
-rw-r--r--spec/javascripts/sidebar/assignee_title_spec.js25
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js66
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb13
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb4
-rw-r--r--spec/models/merge_request_spec.rb36
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb7
-rw-r--r--spec/requests/api/variables_spec.rb11
-rw-r--r--spec/services/projects/destroy_service_spec.rb5
-rw-r--r--spec/support/filter_item_select_helper.rb19
-rw-r--r--spec/support/login_helpers.rb21
-rw-r--r--spec/support/stub_env.rb8
106 files changed, 2945 insertions, 968 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 905c588d338..0fe49ad4579 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 9.3.1 (2017-06-26)
+
+- Fix reversed breadcrumb order for nested groups. !12322
+- Fix 500 when failing to create private group. !12394
+- Fix linking to line number on side-by-side diff creating empty discussion box.
+- Don't match tilde and exclamation mark as part of requirements.txt package name.
+- Perform project housekeeping after importing projects.
+- Fixed ctrl+enter not submit issue edit form.
+
## 9.3.0 (2017-06-22)
- Refactored gitlab:app:check into SystemCheck liberary and improve some checks. !9173
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8b6c87ae518..89e505709a3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -49,6 +49,8 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._
Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is efficient for everyone.
+Looking for something to work on? Look for the label [Accepting Merge Requests](#i-want-to-contribute).
+
GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
source edition, and GitLab Enterprise Edition (EE) which is our commercial
edition. Throughout this guide you will see references to CE and EE for
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index ab0fa336dd0..c20c645d7e4 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.0.5
+5.0.6
diff --git a/Gemfile b/Gemfile
index 6c53bfe6efe..42b75af3a5c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,6 +2,7 @@ source 'https://rubygems.org'
gem 'rails', '4.2.8'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
+gem 'bootsnap', '~> 1.1'
# Responders respond_to and respond_with
gem 'responders', '~> 2.0'
@@ -256,7 +257,7 @@ gem 'base32', '~> 0.3.0'
# Sentry integration
gem 'sentry-raven', '~> 2.4.0'
-gem 'premailer-rails', '~> 1.9.0'
+gem 'premailer-rails', '~> 1.9.7'
# I18n
gem 'ruby_parser', '~> 3.8', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 7c9dd051211..d77ba37f16f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -83,6 +83,8 @@ GEM
bindata (2.3.5)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
+ bootsnap (1.1.1)
+ msgpack (~> 1.0)
bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
@@ -137,7 +139,7 @@ GEM
crack (0.4.3)
safe_yaml (~> 1.0.0)
creole (0.5.0)
- css_parser (1.4.1)
+ css_parser (1.5.0)
addressable
d3_rails (3.5.11)
railties (>= 3.1.0)
@@ -463,6 +465,7 @@ GEM
minitest (5.7.0)
mmap2 (2.2.6)
mousetrap-rails (1.4.6)
+ msgpack (1.1.0)
multi_json (1.12.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
@@ -589,10 +592,11 @@ GEM
websocket-driver (>= 0.2.0)
posix-spawn (0.3.11)
powerpack (0.1.1)
- premailer (1.8.6)
- css_parser (>= 1.3.6)
+ premailer (1.10.4)
+ addressable
+ css_parser (>= 1.4.10)
htmlentities (>= 4.0.0)
- premailer-rails (1.9.2)
+ premailer-rails (1.9.7)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
prometheus-client-mmap (0.7.0.beta5)
@@ -926,6 +930,7 @@ DEPENDENCIES
benchmark-ips (~> 2.3.0)
better_errors (~> 2.1.0)
binding_of_caller (~> 0.7.2)
+ bootsnap (~> 1.1)
bootstrap-sass (~> 3.3.0)
bootstrap_form (~> 2.7.0)
brakeman (~> 3.6.0)
@@ -1045,7 +1050,7 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2)
poltergeist (~> 1.9.0)
- premailer-rails (~> 1.9.0)
+ premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta5)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
diff --git a/app/assets/images/new_nav.png b/app/assets/images/new_nav.png
new file mode 100644
index 00000000000..8879d26d341
--- /dev/null
+++ b/app/assets/images/new_nav.png
Binary files differ
diff --git a/app/assets/images/old_nav.png b/app/assets/images/old_nav.png
new file mode 100644
index 00000000000..23fae7aa19e
--- /dev/null
+++ b/app/assets/images/old_nav.png
Binary files differ
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 88b4b567fa9..31a86090242 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -55,6 +55,7 @@ import RefSelectDropdown from './ref_select_dropdown';
import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
import initSettingsPanels from './settings_panels';
+import initExperimentalFlags from './experimental_flags';
(function() {
var Dispatcher;
@@ -120,6 +121,9 @@ import initSettingsPanels from './settings_panels';
}
switch (page) {
+ case 'profiles:preferences:show':
+ initExperimentalFlags();
+ break;
case 'sessions:new':
new UsernameValidator();
new ActiveTabMemoizer();
diff --git a/app/assets/javascripts/experimental_flags.js b/app/assets/javascripts/experimental_flags.js
new file mode 100644
index 00000000000..dbd3843cef7
--- /dev/null
+++ b/app/assets/javascripts/experimental_flags.js
@@ -0,0 +1,11 @@
+import Cookies from 'js-cookie';
+
+export default () => {
+ $('.js-experiment-feature-toggle').on('change', (e) => {
+ const el = e.target;
+
+ Cookies.set(el.name, el.value, {
+ expires: 365 * 10,
+ });
+ });
+};
diff --git a/app/assets/javascripts/locale/eo/app.js b/app/assets/javascripts/locale/eo/app.js
new file mode 100644
index 00000000000..55f000e9b88
--- /dev/null
+++ b/app/assets/javascripts/locale/eo/app.js
@@ -0,0 +1 @@
+var locales = locales || {}; locales['eo'] = {"domain":"app","locale_data":{"app":{"":{"Project-Id-Version":"gitlab 1.0.0","Report-Msgid-Bugs-To":"","POT-Creation-Date":"2017-06-15 21:59-0500","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","PO-Revision-Date":"2017-06-20 06:24-0400","Last-Translator":"Lyubomir Vasilev <lyubomirv@abv.bg>","Language-Team":"Esperanto (https://translate.zanata.org/project/view/GitLab)","Language":"eo","X-Generator":"Zanata 3.9.6","Plural-Forms":"nplurals=2; plural=(n != 1)","lang":"eo","domain":"app","plural_forms":"nplurals=2; plural=(n != 1)"},"%{commit_author_link} committed %{commit_timeago}":["%{commit_author_link} enmetis %{commit_timeago}"],"About auto deploy":["Pri la aŭtomata disponigado"],"Active":["Aktiva"],"Activity":["Aktiveco"],"Add Changelog":["Aldoni liston de ŝanĝoj"],"Add Contribution guide":["Aldoni gvidliniojn por kontribuado"],"Add License":["Aldoni rajtigilon"],"Add an SSH key to your profile to pull or push via SSH.":["Aldonu SSH-ŝlosilon al via profilo por ebligi al vi eltiri kaj alpuŝi per SSH."],"Add new directory":["Aldoni novan dosierujon"],"Archived project! Repository is read-only":["Arkivita projekto! La deponejo permesas nur legadon"],"Are you sure you want to delete this pipeline schedule?":["Ĉu vi certe volas forigi ĉi tiun ĉenstablan planon?"],"Attach a file by drag &amp; drop or %{upload_link}":["Alkroĉu dosieron per ŝovmetado aŭ %{upload_link}"],"Branch":["Branĉo","Branĉoj"],"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}":["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}"],"Branches":["Branĉoj"],"Browse files":["Elekti dosierojn"],"ByAuthor|by":["de"],"CI configuration":["Agordoj de seninterrompa integrado"],"Cancel":["Nuligi"],"ChangeTypeActionLabel|Pick into branch":["Elekti en branĉon"],"ChangeTypeActionLabel|Revert in branch":["Malfari en branĉo"],"ChangeTypeAction|Cherry-pick":["Precize elekti"],"ChangeTypeAction|Revert":["Malfari"],"Changelog":["Listo de ŝanĝoj"],"Charts":["Diagramoj"],"Cherry-pick this commit":["Precize elekti ĉi tiun kunmetadon"],"Cherry-pick this merge request":["Precize elekti ĉi tiun peton pri kunfando"],"CiStatusLabel|canceled":["nuligita"],"CiStatusLabel|created":["kreita"],"CiStatusLabel|failed":["malsukcesa"],"CiStatusLabel|manual action":["mana ago"],"CiStatusLabel|passed":["sukcesa"],"CiStatusLabel|passed with warnings":["sukcesa, kun avertoj"],"CiStatusLabel|pending":["okazonta"],"CiStatusLabel|skipped":["transsaltita"],"CiStatusLabel|waiting for manual action":["atendanta manan agon"],"CiStatusText|blocked":["blokita"],"CiStatusText|canceled":["nuligita"],"CiStatusText|created":["kreita"],"CiStatusText|failed":["malsukcesa"],"CiStatusText|manual":["mana"],"CiStatusText|passed":["sukcesa"],"CiStatusText|pending":["okazonta"],"CiStatusText|skipped":["transsaltita"],"CiStatus|running":["plenumiĝanta"],"Commit":["Enmetado","Enmetadoj"],"Commit message":["Mesaĝo pri la enmetado"],"CommitBoxTitle|Commit":["Enmeti"],"CommitMessage|Add %{file_name}":["Aldoni „%{file_name}“"],"Commits":["Enmetadoj"],"Commits|History":["Historio"],"Committed by":["Enmetita de"],"Compare":["Kompari"],"Contribution guide":["Gvidlinioj por kontribuado"],"Contributors":["Kontribuantoj"],"Copy URL to clipboard":["Kopii la adreson en la kopibufron"],"Copy commit SHA to clipboard":["Kopii la identigilon de la enmetado"],"Create New Directory":["Krei novan dosierujon"],"Create directory":["Krei dosierujon"],"Create empty bare repository":["Krei malplenan deponejon"],"Create merge request":["Krei peton pri kunfando"],"Create new...":["Krei novan…"],"CreateNewFork|Fork":["Disbranĉigi"],"CreateTag|Tag":["Etikedo"],"Cron Timezone":["Horzono por Cron"],"Cron syntax":["La sintakso de Cron"],"Custom notification events":["Propraj sciigaj eventoj"],"Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.":["La propraj sciigaj niveloj estas la samaj kiel la niveloj de partoprenado. Uzante la proprajn sciigajn nivelojn, vi ricevos ankaŭ sciigojn por elektitaj de vi eventoj. Por lerni pli, bonvolu vidi %{notification_link}."],"Cycle Analytics":["Cikla analizo"],"Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.":["La cikla analizo esploras kiom da tempo necesas por disvolvi ideon ĝis ĝi fariĝos realaĵo."],"CycleAnalyticsStage|Code":["Programado"],"CycleAnalyticsStage|Issue":["Problemo"],"CycleAnalyticsStage|Plan":["Plano"],"CycleAnalyticsStage|Production":["Eldonado"],"CycleAnalyticsStage|Review":["Kontrolo"],"CycleAnalyticsStage|Staging":["Preparo por eldono"],"CycleAnalyticsStage|Test":["Testado"],"Define a custom pattern with cron syntax":["Difini propran ŝablonon, uzante la sintakson de Cron"],"Delete":["Forigi"],"Deploy":["Disponigado","Disponigadoj"],"Description":["Priskribo"],"Directory name":["Nomo de dosierujo"],"Don't show again":["Ne montru denove"],"Download":["Elŝuti"],"Download tar":["Elŝuti en formato „tar“"],"Download tar.bz2":["Elŝuti en formato „tar.bz2“"],"Download tar.gz":["Elŝuti en formato „tar.gz“"],"Download zip":["Elŝuti en formato „zip“"],"DownloadArtifacts|Download":["Elŝuti"],"DownloadCommit|Email Patches":["Sendi flikaĵojn per retpoŝto"],"DownloadCommit|Plain Diff":["Normala dosiero kun diferencoj"],"DownloadSource|Download":["Elŝuti"],"Edit":["Redakti"],"Edit Pipeline Schedule %{id}":["Redakti ĉenstablan planon %{id}"],"Every day (at 4:00am)":["Ĉiutage (je 4:00)"],"Every month (on the 1st at 4:00am)":["Ĉiumonate (en la 1a de la monato, je 4:00)"],"Every week (Sundays at 4:00am)":["Ĉiusemajne (en dimanĉo, je 4:00)"],"Failed to change the owner":["Ne eblas ŝanĝi la posedanton"],"Failed to remove the pipeline schedule":["Ne eblas forigi la ĉenstablan planon"],"Files":["Dosieroj"],"Find by path":["Trovi per dosierindiko"],"Find file":["Trovi dosieron"],"FirstPushedBy|First":["Unue"],"FirstPushedBy|pushed by":["alpuŝita de"],"Fork":["Disbranĉigo","Disbranĉigoj"],"ForkedFromProjectPath|Forked from":["Disbranĉigita el"],"From issue creation until deploy to production":["De la kreado de la problemo ĝis la disponigado en la publika versio"],"From merge request merge until deploy to production":["De la kunfandado de la peto pri kunfando ĝis la disponigado en la publika versio"],"Go to your fork":["Al via disbranĉigo"],"GoToYourFork|Fork":["Disbranĉigo"],"Home":["Hejmo"],"Housekeeping successfully started":["La refreŝigo komenciĝis sukcese"],"Import repository":["Enporti deponejon"],"Interval Pattern":["Intervala ŝablono"],"Introducing Cycle Analytics":["Ni prezentas al vi la ciklan analizon"],"LFSStatus|Disabled":["Malŝaltita"],"LFSStatus|Enabled":["Ŝaltita"],"Last %d day":["La lasta %d tago","La lastaj %d tagoj"],"Last Pipeline":["Lasta ĉenstablo"],"Last Update":["Lasta ĝisdatigo"],"Last commit":["Lasta enmetado"],"Learn more in the":["Lernu pli en la"],"Learn more in the|pipeline schedules documentation":["dokumentado pri ĉenstablaj planoj"],"Leave group":["Forlasi la grupon"],"Leave project":["Forlasi la projekton"],"Limited to showing %d event at most":["Limigita al montrado de ne pli ol %d evento","Limigita al montrado de ne pli ol %d eventoj"],"Median":["Mediano"],"MissingSSHKeyWarningLink|add an SSH key":["aldonos SSH-ŝlosilon"],"New Issue":["Nova problemo","Novaj problemoj"],"New Pipeline Schedule":["Nova ĉenstabla plano"],"New branch":["Nova branĉo"],"New directory":["Nova dosierujo"],"New file":["Nova dosiero"],"New issue":["Nova problemo"],"New merge request":["Nova peto pri kunfando"],"New schedule":["Nova plano"],"New snippet":["Nova kodaĵo"],"New tag":["Nova etikedo"],"No repository":["Ne estas deponejo"],"No schedules":["Ne estas planoj"],"Not available":["Ne disponebla"],"Not enough data":["Ne estas sufiĉe da datenoj"],"Notification events":["Sciigaj eventoj"],"NotificationEvent|Close issue":["Fermi problemon"],"NotificationEvent|Close merge request":["Fermi peton pri kunfando"],"NotificationEvent|Failed pipeline":["Malsukcesa ĉenstablo"],"NotificationEvent|Merge merge request":["Apliki peton pri kunfando"],"NotificationEvent|New issue":["Nova problemo"],"NotificationEvent|New merge request":["Nova peto pri kunfando"],"NotificationEvent|New note":["Nova noto"],"NotificationEvent|Reassign issue":["Reatribui problemon"],"NotificationEvent|Reassign merge request":["Reatribui peton pri kunfando"],"NotificationEvent|Reopen issue":["Remalfermi problemon"],"NotificationEvent|Successful pipeline":["Sukcesa ĉenstablo"],"NotificationLevel|Custom":["Propraj"],"NotificationLevel|Disabled":["Malŝaltitaj"],"NotificationLevel|Global":["Ĝeneralaj"],"NotificationLevel|On mention":["Ĉe mencio"],"NotificationLevel|Participate":["Partoprenado"],"NotificationLevel|Watch":["Rigardado"],"OfSearchInADropdown|Filter":["Filtrilo"],"OpenedNDaysAgo|Opened":["Malfermita"],"Options":["Opcioj"],"Owner":["Posedanto"],"Pipeline":["Ĉenstablo"],"Pipeline Health":["Stato"],"Pipeline Schedule":["Ĉenstabla plano"],"Pipeline Schedules":["Ĉenstablaj planoj"],"PipelineSchedules|Activated":["Ŝaltita"],"PipelineSchedules|Active":["Ŝaltitaj"],"PipelineSchedules|All":["Ĉiuj"],"PipelineSchedules|Inactive":["Malŝaltitaj"],"PipelineSchedules|Next Run":["Sekvanta plenumo"],"PipelineSchedules|None":["Nenio"],"PipelineSchedules|Provide a short description for this pipeline":["Entajpu mallongan priskribon pri ĉi tiu ĉenstablo"],"PipelineSchedules|Take ownership":["Akiri posedon"],"PipelineSchedules|Target":["Celo"],"PipelineSheduleIntervalPattern|Custom":["Propra"],"Pipeline|with stage":["kun etapo"],"Pipeline|with stages":["kun etapoj"],"Project '%{project_name}' queued for deletion.":["La projekto „%{project_name}“ estis alvicigita por forigado."],"Project '%{project_name}' was successfully created.":["La projekto „%{project_name}“ estis sukcese kreita."],"Project '%{project_name}' was successfully updated.":["La projekto „%{project_name}“ estis sukcese ĝisdatigita."],"Project '%{project_name}' will be deleted.":["La projekto „%{project_name}“ estos forigita."],"Project access must be granted explicitly to each user.":["Ĉiu uzanto devas akiri propran atingon al la projekto."],"Project export could not be deleted.":["Ne eblas forigi la projektan elporton."],"Project export has been deleted.":["La projekta elporto estis forigita."],"Project export link has expired. Please generate a new export from your project settings.":["La ligilo por la projekta elporto eksvalidiĝis. Bonvolu krei novan elporton en la agordoj de la projekto."],"Project export started. A download link will be sent by email.":["La elporto de la projekto komenciĝis. Vi ricevos ligilon per retpoŝto por elŝuti la datenoj."],"Project home":["Hejmo de la projekto"],"ProjectFeature|Disabled":["Malŝaltita"],"ProjectFeature|Everyone with access":["Ĉiu, kiu havas atingon"],"ProjectFeature|Only team members":["Nur skipanoj"],"ProjectFileTree|Name":["Nomo"],"ProjectLastActivity|Never":["Neniam"],"ProjectLifecycle|Stage":["Etapo"],"ProjectNetworkGraph|Graph":["Grafeo"],"Read more":["Legu pli"],"Readme":["LeguMin"],"RefSwitcher|Branches":["Branĉoj"],"RefSwitcher|Tags":["Etikedoj"],"Related Commits":["Rilataj enmetadoj"],"Related Deployed Jobs":["Rilataj disponigitaj taskoj"],"Related Issues":["Rilataj problemoj"],"Related Jobs":["Rilataj taskoj"],"Related Merge Requests":["Rilataj petoj pri kunfando"],"Related Merged Requests":["Rilataj aplikitaj petoj pri kunfando"],"Remind later":["Rememorigu denove"],"Remove project":["Forigi la projekton"],"Request Access":["Peti atingeblon"],"Revert this commit":["Malfari ĉi tiun enmetadon"],"Revert this merge request":["Malfari ĉi tiun peton pri kunfando"],"Save pipeline schedule":["Konservi ĉenstablan planon"],"Schedule a new pipeline":["Plani novan ĉenstablon"],"Scheduling Pipelines":["Planado de la ĉenstabloj"],"Search branches and tags":["Serĉu branĉon aŭ etikedon"],"Select Archive Format":["Elektu formaton de arkivo"],"Select a timezone":["Elektu horzonon"],"Select target branch":["Elektu celan branĉon"],"Set a password on your account to pull or push via %{protocol}":["Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuŝi per %{protocol}"],"Set up CI":["Agordi SI"],"Set up Koding":["Agordi „Koding“"],"Set up auto deploy":["Agordi aŭtomatan disponigadon"],"SetPasswordToCloneLink|set a password":["kreos pasvorton"],"Showing %d event":["Estas montrata %d evento","Estas montrataj %d eventoj"],"Source code":["Kodo"],"StarProject|Star":["Steligi"],"Start a %{new_merge_request} with these changes":["Kreu %{new_merge_request} kun ĉi tiuj ŝanĝoj"],"Switch branch/tag":["Iri al branĉo/etikedo"],"Tag":["Etikedo","Etikedoj"],"Tags":["Etikedoj"],"Target Branch":["Cela branĉo"],"The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.":["La etapo de programado montras la tempon de la unua enmetado ĝis la kreado de la peto pri kunfando. La datenoj aldoniĝos aŭtomate ĉi tie post kiam vi kreas la unuan peton pri kunfando."],"The collection of events added to the data gathered for that stage.":["La aro da eventoj, kiuj estas aldonitaj al la datenoj kolektitaj por la etapo."],"The fork relationship has been removed.":["La rilato de disbranĉigo estis forigita."],"The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.":["La etapo de la problemo montras kiom la tempo pasas de la kreado de problemo ĝis la atribuado de la problemo al cela etapo de la projekto, aŭ al listo sur la problemtabulo. Komencu krei problemojn por vidi la datenojn por ĉi tiu etapo."],"The phase of the development lifecycle.":["La etapo de la disvolva ciklo."],"The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user.":["La ĉenstabla plano plenumas ĉenstablojn en la estonteco, ripete, por difinitaj branĉoj aŭ etikedoj. Tiuj planitaj ĉenstabloj heredos la limigitan atingon al la projekto de la rilata uzanto."],"The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.":["La etapo de la plano montras la tempon de la antaŭa ŝtupo ĝis la alpuŝado de via unua enmetado. Ĉi tiu tempo aldoniĝos aŭtomate post kiam vi alpuŝas la unuan enmetadon."],"The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.":["La etapo de eldonado montras la tutan tempon de la kreado de problemo ĝis la disponigado en la publika versio. La datenoj aldoniĝos aŭtomate post kiam vi kompletigos plenan ciklon de ideo ĝis realaĵo."],"The project can be accessed by any logged in user.":["Ĉiu ensalutita uzanto havas atingon al la projekto"],"The project can be accessed without any authentication.":["Ĉiu povas havi atingon al la projekto, sen ensaluti"],"The repository for this project does not exist.":["La deponejo por ĉi tiu projekto ne ekzistas."],"The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.":["La etapo de la kontrolo montras la tempon de la kreado de la peto pri kunfando ĝis ĝia aplikado. La datenoj aldoniĝos aŭtomate post kiam vi aplikos la unuan peton pri kunfando."],"The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.":["La etapo de preparo por eldono montras la tempon inter la aplikado de la peto pri kunfando kaj la disponigado de la kodo en la publika versio. La datenoj aldoniĝos aŭtomate post kiam vi faros la unuan disponigadon en la publika versio."],"The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.":["La etapo de testado montras kiom da tempo necesas al „GitLab CI“ por plenumi ĉiujn ĉenstablojn por la rilata peto pri kunfando. La datenoj aldoniĝos aŭtomate post kiam via unua ĉenstablo finiĝos."],"The time taken by each data entry gathered by that stage.":["La tempo, kiu estas necesa por ĉiu dateno kolektita de la etapo."],"The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6.":["La valoro, kiu troviĝas en la mezo de aro da rigardataj valoroj. Ekzemple: inter 3, 5 kaj 9, la mediano estas 5. Inter 3, 5, 7 kaj 8, la mediano estas (5+7)/2 = 6."],"This means you can not push code until you create an empty repository or import existing one.":["Ĉi tiu signifas, ke vi ne povos alpuŝi kodon, antaŭ ol vi kreos malplenan deponejon aŭ enportos jam ekzistantan."],"Time before an issue gets scheduled":["Tempo antaŭ problemo estas planita por ellabori"],"Time before an issue starts implementation":["Tempo antaŭ la komenco de laboro super problemo"],"Time between merge request creation and merge/close":["Tempo inter la kreado de poeto pri kunfando kaj ĝia aplikado/fermado"],"Time until first merge request":["Tempo ĝis la unua peto pri kunfando"],"Timeago|%s days ago":["antaŭ %s tagoj"],"Timeago|%s days remaining":["restas %s tagoj"],"Timeago|%s hours remaining":["restas %s horoj"],"Timeago|%s minutes ago":["antaŭ %s minutoj"],"Timeago|%s minutes remaining":["restas %s minutoj"],"Timeago|%s months ago":["antaŭ %s monatoj"],"Timeago|%s months remaining":["restas %s monatoj"],"Timeago|%s seconds remaining":["restas %s sekundoj"],"Timeago|%s weeks ago":["antaŭ %s semajnoj"],"Timeago|%s weeks remaining":["restas %s semajnoj"],"Timeago|%s years ago":["antaŭ %s jaroj"],"Timeago|%s years remaining":["restas %s jaroj"],"Timeago|1 day remaining":["restas 1 tago"],"Timeago|1 hour remaining":["restas 1 horo"],"Timeago|1 minute remaining":["restas 1 minuto"],"Timeago|1 month remaining":["restas 1 monato"],"Timeago|1 week remaining":["restas 1 semajno"],"Timeago|1 year remaining":["restas 1 jaro"],"Timeago|Past due":["Malfruiĝis"],"Timeago|a day ago":["antaŭ unu tago"],"Timeago|a month ago":["antaŭ unu monato"],"Timeago|a week ago":["antaŭ unu semajno"],"Timeago|a while":["antaŭ iom da tempo"],"Timeago|a year ago":["antaŭ unu jaro"],"Timeago|about %s hours ago":["antaŭ ĉirkaŭ %s horoj"],"Timeago|about a minute ago":["antaŭ ĉirkaŭ unu minuto"],"Timeago|about an hour ago":["antaŭ ĉirkaŭ unu horo"],"Timeago|in %s days":["post %s tagoj"],"Timeago|in %s hours":["post %s horoj"],"Timeago|in %s minutes":["post %s minutoj"],"Timeago|in %s months":["post %s monatoj"],"Timeago|in %s seconds":["post %s sekundoj"],"Timeago|in %s weeks":["post %s semajnoj"],"Timeago|in %s years":["post %s jaroj"],"Timeago|in 1 day":["post 1 tago"],"Timeago|in 1 hour":["post 1 horo"],"Timeago|in 1 minute":["post 1 minuto"],"Timeago|in 1 month":["post 1 monato"],"Timeago|in 1 week":["post 1 semajno"],"Timeago|in 1 year":["post 1 jaro"],"Timeago|less than a minute ago":["antaŭ malpli ol minuto"],"Time|hr":["h","h"],"Time|min":["min","min"],"Time|s":["s"],"Total Time":["Totala tempo"],"Total test time for all commits/merges":["Totala tempo por la testado de ĉiuj enmetadoj/kunfandoj"],"Unstar":["Malsteligi"],"Upload New File":["Alŝuti novan dosieron"],"Upload file":["Alŝuti dosieron"],"Use your global notification setting":["Uzi vian ĝeneralan agordon pri la sciigoj"],"VisibilityLevel|Internal":["Interna"],"VisibilityLevel|Private":["Privata"],"VisibilityLevel|Public":["Publika"],"Want to see the data? Please ask an administrator for access.":["Ĉu vi volas vidi la datenojn? Bonvolu peti atingeblon de administranto."],"We don't have enough data to show this stage.":["Ne estas sufiĉe da datenoj por montri ĉi tiun etapon."],"Withdraw Access Request":["Nuligi la peton pri atingeblo"],"You are going to remove %{project_name_with_namespace}.\\nRemoved project CANNOT be restored!\\nAre you ABSOLUTELY sure?":["Vi forigos „%{project_name_with_namespace}“.\\nOni NE POVAS malfari la forigon de projekto!\\nĈu vi estas ABSOLUTE certa?"],"You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?":["Vi forigos la rilaton de la disbranĉigo al la originala projekto, „%{forked_from_project}“. Ĉu vi estas ABSOLUTE certa?"],"You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?":["Vi transigos „%{project_name_with_namespace}“ al alia posedanto. Ĉu vi estas ABSOLUTE certa?"],"You can only add files when you are on a branch":["Oni povas aldoni dosierojn nur kiam oni estas en branĉo"],"You have reached your project limit":["Vi ne povas krei pliajn projektojn"],"You must sign in to star a project":["Oni devas ensaluti por steligi projekton"],"You need permission.":["VI bezonas permeson."],"You will not get any notifications via email":["VI ne ricevos sciigojn per retpoŝto"],"You will only receive notifications for the events you choose":["Vi ricevos sciigojn nur por la eventoj elektitaj de vi"],"You will only receive notifications for threads you have participated in":["Vi ricevos sciigojn nur por la fadenoj, en kiuj vi partoprenis"],"You will receive notifications for any activity":["Vi ricevos sciigojn por ĉiu ago"],"You will receive notifications only for comments in which you were @mentioned":["Vi ricevos sciigojn nur por komentoj, en kiuj vi estas @menciita"],"You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account":["Vi ne povos eltiri aŭ alpuŝi kodon per %{protocol} antaŭ ol vi %{set_password_link} por via konto"],"You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile":["Vi ne povos eltiri aŭ alpuŝi kodon per SSH antaŭ ol vi %{add_ssh_key_link} al via profilo"],"Your name":["Via nomo"],"day":["tago","tagoj"],"new merge request":["novan peton pri kunfando"],"notification emails":["sciigoj per retpoŝto"],"parent":["patro","patroj"]}}}; \ No newline at end of file
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index ed7629948ca..d27b4ec78c6 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -299,9 +299,10 @@ $(function () {
// Commit show suppressed diff
});
$('.navbar-toggle').on('click', function () {
- $('.header-content .title').toggle();
+ $('.header-content .title, .header-content .navbar-sub-nav').toggle();
$('.header-content .header-logo').toggle();
$('.header-content .navbar-collapse').toggle();
+ $('.js-navbar-toggle-left, .js-navbar-toggle-right, .title-container').toggle();
return $('.navbar-toggle').toggleClass('active');
});
// Show/hide comments on diff
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index e98f35bb58c..87b2725a045 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -40,7 +40,6 @@ export default {
return {
isLoading: false,
dropdownContent: '',
- endpoint: this.stage.dropdown_path,
};
},
@@ -73,7 +72,7 @@ export default {
},
fetchJobs() {
- this.$http.get(this.endpoint)
+ this.$http.get(this.stage.dropdown_path)
.then((response) => {
this.dropdownContent = response.json().html;
this.isLoading = false;
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js b/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
index a9ad3708514..5a6e47e566e 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
@@ -14,6 +14,11 @@ export default {
type: Boolean,
required: true,
},
+ showToggle: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
assigneeTitle() {
@@ -36,6 +41,19 @@ export default {
>
Edit
</a>
+ <a
+ v-if="showToggle"
+ aria-label="Toggle sidebar"
+ class="gutter-toggle pull-right js-sidebar-toggle"
+ href="#"
+ role="button"
+ >
+ <i
+ aria-hidden="true"
+ data-hidden="true"
+ class="fa fa-angle-double-right"
+ />
+ </a>
</div>
`,
};
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
index da4abf0b68f..f83c3b037ed 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
@@ -64,6 +64,7 @@ export default {
},
beforeMount() {
this.field = this.$el.dataset.field;
+ this.signedIn = typeof this.$el.dataset.signedIn !== 'undefined';
},
template: `
<div>
@@ -71,6 +72,7 @@ export default {
:number-of-assignees="store.assignees.length"
:loading="loading || store.isFetching.assignees"
:editable="store.editable"
+ :show-toggle="!signedIn"
/>
<assignees
v-if="!store.isFetching.assignees"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
index c02e10128e2..e8b3cf2f729 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
@@ -17,6 +17,9 @@ export default {
return hasCI && !ciStatus;
},
+ hasPipeline() {
+ return Object.keys(this.mr.pipeline || {}).length > 0;
+ },
svg() {
return statusIconEntityMap.icon_status_failed;
},
@@ -30,7 +33,11 @@ export default {
template: `
<div class="mr-widget-heading">
<div class="ci-widget">
- <template v-if="hasCIError">
+ <template v-if="!hasPipeline">
+ <i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i>
+ Waiting for pipeline...
+ </template>
+ <template v-else-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error">
<span class="js-icon-link icon-link">
<span
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 94986badaf9..5bd6c095109 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -34,6 +34,8 @@ header {
top: 0;
left: 0;
right: 0;
+ color: $gl-text-color-secondary;
+ border-radius: 0;
@media (max-width: $screen-xs-min) {
padding: 0 16px;
@@ -59,7 +61,7 @@ header {
padding: 0;
.nav > li > a {
- color: $gl-text-color-secondary;
+ color: currentColor;
font-size: 18px;
padding: 0;
margin: (($header-height - 28) / 2) 3px;
@@ -84,7 +86,7 @@ header {
&:hover,
&:focus,
&:active {
- background-color: $gray-light;
+ background-color: transparent;
color: $gl-text-color;
svg {
@@ -96,13 +98,19 @@ header {
font-size: 14px;
}
+ .fa-chevron-down {
+ position: relative;
+ top: -3px;
+ font-size: 10px;
+ }
+
svg {
position: relative;
top: 2px;
height: 17px;
// hack to get SVG to line up with FA icons
width: 23px;
- fill: $gl-text-color-secondary;
+ fill: currentColor;
}
}
@@ -225,7 +233,7 @@ header {
}
a {
- color: $gl-text-color;
+ color: currentColor;
&:hover {
text-decoration: underline;
@@ -346,6 +354,7 @@ header {
width: auto;
min-width: 140px;
margin-top: -5px;
+ color: $gl-text-color;
left: auto;
.current-user {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 476427c1593..630f557602c 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -74,6 +74,10 @@ $red-700: #a62d19;
$red-800: #8b2615;
$red-900: #711e11;
+$purple-700: #4a2192;
+$purple-800: #2c0a5c;
+$purple-900: #380d75;
+
$black: #000;
$black-transparent: rgba(0, 0, 0, 0.3);
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
new file mode 100644
index 00000000000..441bfc479f6
--- /dev/null
+++ b/app/assets/stylesheets/new_nav.scss
@@ -0,0 +1,267 @@
+@import "framework/variables";
+@import 'framework/tw_bootstrap_variables';
+@import "bootstrap/variables";
+
+header.navbar-gitlab-new {
+ color: $white-light;
+ background-color: $purple-900;
+ border-bottom: 0;
+
+ .header-content {
+ padding-left: 0;
+
+ .title-container {
+ padding-top: 0;
+ overflow: visible;
+ }
+
+ .title {
+ display: block;
+ height: 100%;
+ padding-right: 0;
+ color: currentColor;
+
+ > a {
+ display: flex;
+ align-items: center;
+ height: 100%;
+ padding-top: 3px;
+ padding-right: $gl-padding;
+ padding-left: $gl-padding;
+ margin-left: -$gl-padding;
+ border-bottom: 3px solid transparent;
+
+ @media (min-width: $screen-sm-min) {
+ padding-right: $gl-padding;
+ padding-left: $gl-padding;
+ }
+
+ svg {
+ margin-top: -3px;
+
+ @media (min-width: $screen-sm-min) {
+ margin-right: 10px;
+ }
+ }
+
+ &:hover,
+ &:focus {
+ color: currentColor;
+ text-decoration: none;
+ border-bottom-color: $white-light;
+ }
+ }
+ }
+
+ .dropdown.open {
+ > a {
+ border-bottom-color: $white-light;
+ }
+ }
+
+ .dropdown-menu {
+ margin-top: 4px;
+ min-width: 130px;
+
+ @media (max-width: $screen-xs-max) {
+ left: auto;
+ right: 0;
+ }
+ }
+ }
+
+ .navbar-collapse {
+ padding-left: 0;
+ color: $white-light;
+ box-shadow: 0;
+
+ @media (max-width: $screen-xs-max) {
+ margin-left: -$gl-padding;
+ margin-right: -10px;
+ }
+
+ .dropdown-bold-header {
+ color: initial;
+ }
+
+ .nav {
+ > li:not(.hidden-xs) a {
+ @media (max-width: $screen-xs-max) {
+ margin-left: 0;
+ min-width: 100%;
+ }
+ }
+ }
+ }
+
+ .container-fluid {
+ .navbar-toggle {
+ min-width: 45px;
+ padding: 6px $gl-padding;
+ margin-right: -7px;
+ font-size: 14px;
+ text-align: center;
+ color: currentColor;
+ border-left: 1px solid lighten($purple-700, 10%);
+
+ &:hover,
+ &:focus,
+ &.active {
+ color: currentColor;
+ background-color: transparent;
+ }
+ }
+
+ .navbar-nav {
+ @media (max-width: $screen-xs-max) {
+ display: flex;
+ padding-right: 10px;
+ }
+
+ li {
+ .badge {
+ box-shadow: none;
+ }
+ }
+ }
+
+ .nav > li {
+ &.header-user {
+ @media (max-width: $screen-xs-max) {
+ padding-left: 10px;
+ }
+ }
+
+ > a {
+ background: none;
+ opacity: .9;
+ will-change: opacity;
+
+ &.header-user-dropdown-toggle {
+ .header-user-avatar {
+ border-color: $white-light;
+ }
+ }
+
+ &:hover,
+ &:focus {
+ color: $white-light;
+ opacity: 1;
+
+ > svg {
+ fill: $white-light;
+ }
+
+ &.header-user-dropdown-toggle {
+ .header-user-avatar {
+ border-color: $white-light;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+.navbar-sub-nav {
+ display: flex;
+ margin-bottom: 0;
+ color: $white-light;
+
+ > li {
+ &.active > a,
+ a:hover,
+ a:focus {
+ border-bottom-color: $white-light;
+ text-decoration: none;
+ outline: 0;
+ opacity: 1;
+ }
+
+ > a {
+ display: block;
+ padding: 16px 10px 13px;
+ font-size: 13px;
+ color: currentColor;
+ border-bottom: 3px solid transparent;
+ opacity: .9;
+ will-change: opacity;
+
+ @media (min-width: $screen-sm-min) {
+ padding: 15px $gl-padding 12px;
+ font-size: 14px;
+ }
+ }
+ }
+
+ .dropdown-chevron {
+ position: relative;
+ top: -1px;
+ font-size: 10px;
+ }
+}
+
+.header-user .dropdown-menu-nav,
+.header-new .dropdown-menu-nav {
+ margin-top: 4px;
+}
+
+.search {
+ form {
+ border-color: $purple-800;
+
+ &:hover {
+ border-color: rgba($white-light, .6);
+ box-shadow: none;
+ }
+ }
+
+ &.search-active form {
+ border-color: $white-light;
+ }
+
+ form,
+ .search-input {
+ background-color: $purple-700;
+ }
+
+ .search-input {
+ color: $white-light;
+ }
+
+ .search-input::placeholder {
+ color: rgba($white-light, .6);
+ }
+
+ .location-badge {
+ font-size: 12px;
+ color: rgba($white-light, .6);
+ background-color: $purple-800;
+ transition: color 0.15s;
+ will-change: color;
+ }
+
+ .search-input-wrap {
+ .search-icon,
+ .clear-icon {
+ color: rgba($white-light, .6);
+ }
+ }
+
+ &.search-active {
+ .location-badge {
+ color: $white-light;
+ background-color: $purple-800;
+ }
+
+ .search-input-wrap {
+ .search-icon {
+ color: rgba($white-light, .6);
+ }
+
+ .clear-icon {
+ color: $white-light;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 0d1a360d12c..e3ebcc8af6c 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -228,6 +228,12 @@
padding-top: 10px;
}
+ &:not(.issue-boards-sidebar):not([data-signed-in]) {
+ .issuable-sidebar-header {
+ display: none;
+ }
+ }
+
.assign-yourself .btn-link {
padding-left: 0;
}
@@ -249,6 +255,10 @@
border-left: 1px solid $border-gray-normal;
}
+ .title .gutter-toggle {
+ margin-top: 0;
+ }
+
.assignee .avatar {
float: left;
margin-right: 10px;
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index a3b243fccb7..dc7ff78f3df 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -300,4 +300,12 @@ module ApplicationHelper
"https://www.twitter.com/#{name}"
end
end
+
+ def can_toggle_new_nav?
+ Rails.env.development?
+ end
+
+ def show_new_nav?
+ cookies["new_nav"] == "true"
+ end
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 00464810054..ba84dbe4a7a 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -50,10 +50,17 @@ module ButtonHelper
def http_clone_button(project, placement = 'right', append_link: true)
klass = 'http-selector'
- klass << ' has-tooltip' if current_user.try(:require_password?)
+ klass << ' has-tooltip' if current_user.try(:require_password?) || current_user.try(:require_personal_access_token?)
protocol = gitlab_config.protocol.upcase
+ tooltip_title =
+ if current_user.try(:require_password?)
+ _("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol }
+ else
+ _("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol }
+ end
+
content_tag (append_link ? :a : :span), protocol,
class: klass,
href: (project.http_url_to_repo if append_link),
@@ -61,7 +68,7 @@ module ButtonHelper
html: true,
placement: placement,
container: 'body',
- title: _("Set a password on your account to pull or push via %{protocol}") % { protocol: protocol }
+ title: tooltip_title
}
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 4e6e6805920..6baf6f31d8f 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -133,21 +133,28 @@ module LabelsHelper
end
end
+ def can_subscribe_to_label_in_different_levels?(label)
+ defined?(@project) && label.is_a?(GroupLabel)
+ end
+
def label_subscription_status(label, project)
- return 'project-level' if label.subscribed?(current_user, project)
return 'group-level' if label.subscribed?(current_user)
+ return 'project-level' if label.subscribed?(current_user, project)
'unsubscribed'
end
- def group_label_unsubscribe_path(label, project)
+ def toggle_subscription_label_path(label, project)
+ return toggle_subscription_group_label_path(label.group, label) unless project
+
case label_subscription_status(label, project)
- when 'project-level' then toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)
when 'group-level' then toggle_subscription_group_label_path(label.group, label)
+ when 'project-level' then toggle_subscription_namespace_project_label_path(project.namespace, project, label)
+ when 'unsubscribed' then toggle_subscription_namespace_project_label_path(project.namespace, project, label)
end
end
- def label_subscription_toggle_button_text(label, project)
+ def label_subscription_toggle_button_text(label, project = nil)
label.subscribed?(current_user, project) ? 'Unsubscribe' : 'Subscribe'
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index d10e0bd45b0..c04b1419a19 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -198,6 +198,23 @@ module ProjectsHelper
.load_in_batch_for_projects(projects)
end
+ def show_no_ssh_key_message?
+ cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
+ end
+
+ def show_no_password_message?
+ cookies[:hide_no_password_message].blank? && !current_user.hide_no_password &&
+ ( current_user.require_password? || current_user.require_personal_access_token? )
+ end
+
+ def link_to_set_password
+ if current_user.require_password?
+ link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
+ else
+ link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path
+ end
+ end
+
private
def repo_children_classes(field)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index f581a25f093..c099d731082 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -771,6 +771,7 @@ class MergeRequest < ActiveRecord::Base
"refs/heads/#{source_branch}",
ref_path
)
+ update_column(:ref_fetched, true)
end
def ref_path
@@ -778,7 +779,13 @@ class MergeRequest < ActiveRecord::Base
end
def ref_fetched?
- project.repository.ref_exists?(ref_path)
+ super ||
+ begin
+ computed_value = project.repository.ref_exists?(ref_path)
+ update_column(:ref_fetched, true) if computed_value
+
+ computed_value
+ end
end
def ensure_ref_fetched
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index c9df678f97e..217f753f05f 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -60,7 +60,7 @@ class PrometheusService < MonitoringService
def deployment_metrics(deployment)
metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &method(:rename_data_to_metrics))
- metrics&.merge(deployment_time: created_at.to_i) || {}
+ metrics&.merge(deployment_time: deployment.created_at.to_i) || {}
end
def additional_environment_metrics(environment)
diff --git a/app/models/user.rb b/app/models/user.rb
index 650b64e7551..6dd1b1415d6 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -570,7 +570,13 @@ class User < ActiveRecord::Base
end
def require_password?
- password_automatically_set? && !ldap_user?
+ password_automatically_set? && !ldap_user? && current_application_settings.signin_enabled?
+ end
+
+ def require_personal_access_token?
+ return false if current_application_settings.signin_enabled? || ldap_user?
+
+ PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end
def can_change_username?
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index e242e851b4d..2da8f615470 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -58,20 +58,23 @@
%br
- .table-holder
- %table.table
- %thead
- %tr
- %th Type
- %th Runner token
- %th Description
- %th Version
- %th Projects
- %th Jobs
- %th Tags
- %th Last contact
- %th
+ - if @runners.any?
+ .table-holder
+ %table.table
+ %thead
+ %tr
+ %th Type
+ %th Runner token
+ %th Description
+ %th Version
+ %th Projects
+ %th Jobs
+ %th Tags
+ %th Last contact
+ %th
- - @runners.each do |runner|
- = render "admin/runners/runner", runner: runner
- = paginate @runners, theme: "gitlab"
+ - @runners.each do |runner|
+ = render "admin/runners/runner", runner: runner
+ = paginate @runners, theme: "gitlab"
+ - else
+ .nothing-here-block No runners found
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index eea33b5966f..f7a1d7e8844 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -30,6 +30,9 @@
= stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'peek' if peek_enabled?
+ - if show_new_nav?
+ = stylesheet_link_tag "new_nav", media: "all"
+
= Gon::Base.render_data
= webpack_bundle_tag "runtime"
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 2b07273a0a8..d879df8fc82 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -4,7 +4,10 @@
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
= render "layouts/init_auto_complete" if @gfm_form
= render 'peek/bar'
- = render "layouts/header/default", title: header_title
+ - if show_new_nav?
+ = render "layouts/header/new"
+ - else
+ = render "layouts/header/default", title: header_title
= render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 249253f4906..f056c0af968 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -74,6 +74,9 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
+ - if can_toggle_new_nav?
+ %li
+ = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml
new file mode 100644
index 00000000000..c0833c64911
--- /dev/null
+++ b/app/views/layouts/header/_new.html.haml
@@ -0,0 +1,93 @@
+%header.navbar.navbar-gitlab.navbar-gitlab-new{ class: nav_header_class }
+ %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
+ .container-fluid
+ .header-content
+ .title-container
+ %h1.title
+ = link_to root_path, title: 'Dashboard' do
+ = brand_header_logo
+ %span.hidden-xs
+ GitLab
+
+ - if current_user
+ = render "layouts/nav/new_dashboard"
+ - else
+ = render "layouts/nav/new_explore"
+
+ .navbar-collapse.collapse
+ %ul.nav.navbar-nav
+ %li.hidden-sm.hidden-xs
+ = render 'layouts/search' unless current_controller?(:search)
+ %li.visible-sm-inline-block.visible-xs-inline-block
+ = link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('search')
+ - if current_user
+ - if session[:impersonator_id]
+ %li.impersonation
+ = link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = icon('user-secret fw')
+ - if current_user.admin?
+ %li
+ = link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('wrench fw')
+ = render 'layouts/header/new_dropdown'
+ - if Gitlab::Sherlock.enabled?
+ %li
+ = link_to sherlock_transactions_path, title: 'Sherlock Transactions',
+ data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('tachometer fw')
+ %li
+ = link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('hashtag fw')
+ - issues_count = assigned_issuables_count(:issues)
+ %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
+ = number_with_delimiter(issues_count)
+ %li
+ = link_to assigned_mrs_dashboard_path, title: 'Merge requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = custom_icon('mr_bold')
+ - merge_requests_count = assigned_issuables_count(:merge_requests)
+ %span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
+ = number_with_delimiter(merge_requests_count)
+ %li
+ = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('check-circle fw')
+ %span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
+ = todos_count_format(todos_pending_count)
+ %li.header-user.dropdown
+ = link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do
+ = image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar"
+ = icon('chevron-down')
+ .dropdown-menu-nav.dropdown-menu-align-right
+ %ul
+ %li.current-user
+ .user-name.bold
+ = current_user.name
+ @#{current_user.username}
+ %li.divider
+ %li
+ = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
+ %li
+ = link_to "Settings", profile_path
+ %li
+ = link_to "Turn off new nav", profile_preferences_path(anchor: "new-navigation")
+ %li.divider
+ %li
+ = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
+ - else
+ %li
+ %div
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
+
+ %button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
+ %span.sr-only Toggle navigation
+ = icon('ellipsis-v', class: 'js-navbar-toggle-right')
+ = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;')
+
+ = yield :header_content
+
+= render 'shared/outdated_browser'
+
+- if @project && !@project.empty_repo?
+ - if ref = @ref || @project.repository.root_ref
+ :javascript
+ var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}";
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 969c423b032..9ff1164f2ee 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -1,7 +1,11 @@
%li.header-new.dropdown
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do
- = icon('plus fw')
- = icon('caret-down')
+ - if show_new_nav?
+ = icon('plus')
+ = icon('chevron-down')
+ - else
+ = icon('plus fw')
+ = icon('caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
- if @group&.persisted?
diff --git a/app/views/layouts/nav/_new_dashboard.html.haml b/app/views/layouts/nav/_new_dashboard.html.haml
new file mode 100644
index 00000000000..7109baa4dad
--- /dev/null
+++ b/app/views/layouts/nav/_new_dashboard.html.haml
@@ -0,0 +1,33 @@
+%ul.list-unstyled.navbar-sub-nav
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "home"}) do
+ = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
+ Projects
+
+ = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
+ = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do
+ Groups
+
+ = nav_link(path: 'dashboard#activity', html_options: { class: "hidden-xs hidden-sm" }) do
+ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
+ Activity
+
+ %li.dropdown
+ %a{ href: "#", data: { toggle: "dropdown" } }
+ More
+ = icon("chevron-down", class: "dropdown-chevron")
+ .dropdown-menu
+ %ul
+ = nav_link(path: 'dashboard#activity', html_options: { class: "visible-xs visible-sm" }) do
+ = link_to activity_dashboard_path, title: 'Activity' do
+ Activity
+
+ = nav_link(controller: 'dashboard/milestones') do
+ = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do
+ Milestones
+
+ = nav_link(controller: 'dashboard/snippets') do
+ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do
+ Snippets
+ %li.divider
+ %li
+ = link_to "Help", help_path, title: 'About GitLab CE'
diff --git a/app/views/layouts/nav/_new_explore.html.haml b/app/views/layouts/nav/_new_explore.html.haml
new file mode 100644
index 00000000000..40385f251e3
--- /dev/null
+++ b/app/views/layouts/nav/_new_explore.html.haml
@@ -0,0 +1,19 @@
+%ul.list-unstyled.navbar-sub-nav
+ = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
+ = link_to explore_root_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
+ Projects
+ = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
+ = link_to explore_groups_path, title: 'Groups', class: 'dashboard-shortcuts-groups' do
+ Groups
+ %li.dropdown
+ %a{ href: "#", data: { toggle: "dropdown" } }
+ More
+ = icon("chevron-down", class: "dropdown-chevron")
+ .dropdown-menu
+ %ul
+ = nav_link(controller: :snippets) do
+ = link_to explore_snippets_path, title: 'Snippets', class: 'dashboard-shortcuts-snippets' do
+ Snippets
+ %li.divider
+ %li
+ = link_to "Help", help_path, title: 'About GitLab CE'
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 0ff19b3eab1..0b5995415e9 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -15,6 +15,25 @@
.preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, scheme.id
= scheme.name
+ - if can_toggle_new_nav?
+ .col-sm-12
+ %hr
+ .col-lg-3.profile-settings-sidebar#new-navigation
+ %h4.prepend-top-0
+ New Navigation
+ %p
+ This setting allows you to turn on or off the new upcoming navigation concept.
+ = succeed '.' do
+ = link_to 'Learn more', '', target: '_blank'
+ .col-lg-9.syntax-theme
+ = label_tag do
+ .preview= image_tag "old_nav.png"
+ %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
+ Old
+ = label_tag do
+ .preview= image_tag "new_nav.png"
+ %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
+ New
.col-sm-12
%hr
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index 26f3c297260..520696b01c6 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -11,11 +11,14 @@
.table-mobile-header{ role: 'rowheader' } Job
- if deployment.deployable
.table-mobile-content
- = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
- #{deployment.deployable.name} (##{deployment.deployable.id})
+ .flex-truncate-parent
+ .flex-truncate-child
+ = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
+ #{deployment.deployable.name} (##{deployment.deployable.id})
- if deployment.user
- by
- = user_avatar(user: deployment.user, size: 20)
+ %div
+ by
+ = user_avatar(user: deployment.user, size: 20)
.table-section.section-15{ role: 'gridcell' }
.table-mobile-header{ role: 'rowheader' } Created
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 23aa4c29e69..31e2bb11ce8 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -31,8 +31,8 @@
.ci-table.environments{ role: 'grid' }
.gl-responsive-table-row.table-row-header{ role: 'row' }
.table-section.section-10{ role: 'columnheader' } ID
- .table-section.section-40{ role: 'columnheader' } Commit
- .table-section.section-15{ role: 'columnheader' } Job
+ .table-section.section-30{ role: 'columnheader' } Commit
+ .table-section.section-25{ role: 'columnheader' } Job
.table-section.section-15{ role: 'columnheader' } Created
= render @deployments
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index d48923b422a..bda52fe461c 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -14,7 +14,7 @@
= merge_request.to_reference
%span.merge-request-info
%strong
- = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
+ = link_to merge_request.title, merge_request_path(merge_request), class: "row_title"
- unless @issue.project.id == merge_request.target_project.id
in
- project = merge_request.target_project
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index c185e9b73ee..b8a2c5cb87d 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -1,6 +1,7 @@
- label_css_id = dom_id(label)
- status = label_subscription_status(label, @project).inquiry if current_user
- subject = local_assigns[:subject]
+- toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user
%li{ id: label_css_id, data: { id: label.id } }
= render "shared/label_row", label: label
@@ -17,18 +18,18 @@
%li
= link_to_label(label, subject: subject) do
view open issues
- - if current_user && defined?(@project)
+ - if current_user
%li.label-subscription
- - if label.is_a?(ProjectLabel)
- %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
- %span= label_subscription_toggle_button_text(label, @project)
- - else
- %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: group_label_unsubscribe_path(label, @project) } }
+ - if can_subscribe_to_label_in_different_levels?(label)
+ %a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } }
%span Unsubscribe
%a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
%span Subscribe at project level
%a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } }
%span Subscribe at group level
+ - else
+ %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', data: { status: status, url: toggle_subscription_path } }
+ %span= label_subscription_toggle_button_text(label, @project)
- if can?(current_user, :admin_label, label)
%li
@@ -42,14 +43,10 @@
= link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action') do
view open issues
- - if current_user && defined?(@project)
+ - if current_user
.label-subscription.inline
- - if label.is_a?(ProjectLabel)
- %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
- %span= label_subscription_toggle_button_text(label, @project)
- = icon('spinner spin', class: 'label-subscribe-button-loading')
- - else
- %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', class: ('hidden' if status.unsubscribed?), data: { url: group_label_unsubscribe_path(label, @project) } }
+ - if can_subscribe_to_label_in_different_levels?(label)
+ %button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } }
%span Unsubscribe
= icon('spinner spin', class: 'label-subscribe-button-loading')
@@ -59,10 +56,14 @@
= icon('chevron-down')
%ul.dropdown-menu
%li
- %a.js-subscribe-button{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
+ %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
Project level
- %a.js-subscribe-button{ data: { url: toggle_subscription_group_label_path(label.group, label) } }
+ %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } }
Group level
+ - else
+ %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ type: 'button', data: { status: status, url: toggle_subscription_path } }
+ %span= label_subscription_toggle_button_text(label, @project)
+ = icon('spinner spin', class: 'label-subscribe-button-loading')
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
= link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
@@ -76,10 +77,10 @@
%span.sr-only Delete
= icon('trash-o')
- - if current_user && defined?(@project)
- - if label.is_a?(ProjectLabel)
+ - if current_user
+ - if can_subscribe_to_label_in_different_levels?(label)
:javascript
- new gl.ProjectLabelSubscription('##{dom_id(label)} .label-subscription');
+ new gl.GroupLabelSubscription('##{dom_id(label)} .label-subscription');
- else
:javascript
- new gl.GroupLabelSubscription('##{dom_id(label)} .label-subscription');
+ new gl.ProjectLabelSubscription('##{dom_id(label)} .label-subscription');
diff --git a/app/views/shared/_no_password.html.haml b/app/views/shared/_no_password.html.haml
index b561e6dc248..9b1a467df6b 100644
--- a/app/views/shared/_no_password.html.haml
+++ b/app/views/shared/_no_password.html.haml
@@ -1,9 +1,8 @@
-- if cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && current_user.require_password?
+- if show_no_password_message?
.no-password-message.alert.alert-warning
- - set_password_link = link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
- - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: set_password_link }
+ - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: link_to_set_password }
- set_password_message = _("You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account") % translation_params
-
+ = set_password_message.html_safe
.alert-link-group
= link_to _("Don't show again"), profile_path(user: {hide_no_password: true}), method: :put
|
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
index e7815e28017..17ef5327341 100644
--- a/app/views/shared/_no_ssh.html.haml
+++ b/app/views/shared/_no_ssh.html.haml
@@ -1,8 +1,8 @@
-- if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
+- if show_no_ssh_key_message?
.no-ssh-key-message.alert.alert-warning
- add_ssh_key_link = link_to s_('MissingSSHKeyWarningLink|add an SSH key'), profile_keys_path, class: 'alert-link'
- ssh_message = _("You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile") % { add_ssh_key_link: add_ssh_key_link }
- #{ ssh_message.html_safe }
+ = ssh_message.html_safe
.alert-link-group
= link_to _("Don't show again"), profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link'
|
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index e49bd5ebb13..745f1ee62da 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -3,7 +3,7 @@
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('sidebar')
-%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
+%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix", signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
.issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } }
- can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.block.issuable-sidebar-header
@@ -20,7 +20,7 @@
.block.todo.hide-expanded
= render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable, is_collapsed: true
.block.assignee
- = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable
+ = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present?
.block.milestone
.sidebar-collapsed-icon
= icon('clock-o', 'aria-hidden': 'true')
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index bcfa1dc826e..2ea5eb960c0 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -1,5 +1,5 @@
- if issuable.is_a?(Issue)
- #js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]" } }
+ #js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]", signed_in: signed_in } }
.title.hide-collapsed
Assignee
= icon('spinner spin')
@@ -14,6 +14,9 @@
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
+ - if !signed_in
+ %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" }
+ = sidebar_gutter_toggle_icon
.value.hide-collapsed
- if issuable.assignee
= link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 9e6a76e1ddb..680e1f3a4ea 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -4,7 +4,7 @@
%li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
.row
.col-sm-6
- %strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
+ %strong= link_to truncate(milestone.title, length: 100), milestone_path
.col-sm-6
.pull-right.light #{milestone.percent_complete(current_user)}% complete
.row
diff --git a/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml
new file mode 100644
index 00000000000..07c201de96e
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'dashboard/merge_requests' spinach with rspec
+merge_request: 12440
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml
new file mode 100644
index 00000000000..65df9a836a5
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'dashboard/todos' spinach with rspec
+merge_request: 12453
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/33846-no-runner-for-admin.yml b/changelogs/unreleased/33846-no-runner-for-admin.yml
new file mode 100644
index 00000000000..a2d46802c61
--- /dev/null
+++ b/changelogs/unreleased/33846-no-runner-for-admin.yml
@@ -0,0 +1,4 @@
+---
+title: Add explicit message when no runners on admin
+merge_request: 12266
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34010-fix-linking-to-parallel-diff-line-number-creating-gray-box.yml b/changelogs/unreleased/34010-fix-linking-to-parallel-diff-line-number-creating-gray-box.yml
deleted file mode 100644
index 705d44f8b10..00000000000
--- a/changelogs/unreleased/34010-fix-linking-to-parallel-diff-line-number-creating-gray-box.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix linking to line number on side-by-side diff creating empty discussion box
-merge_request:
-author:
diff --git a/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml b/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml
new file mode 100644
index 00000000000..4bacfca7551
--- /dev/null
+++ b/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml
@@ -0,0 +1,4 @@
+---
+title: Store merge request ref_fetched status in the database
+merge_request: 12424
+author:
diff --git a/changelogs/unreleased/34282-fix-api-using-include_missing-false.yml b/changelogs/unreleased/34282-fix-api-using-include_missing-false.yml
new file mode 100644
index 00000000000..e7fff2c1a9f
--- /dev/null
+++ b/changelogs/unreleased/34282-fix-api-using-include_missing-false.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Fix optional arugments for POST :id/variables'
+merge_request: 12474
+author:
diff --git a/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml b/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml
new file mode 100644
index 00000000000..af743f3e506
--- /dev/null
+++ b/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml
@@ -0,0 +1,4 @@
+---
+title: Add Esperanto translations for Cycle Analytics, Project, and Repository pages
+merge_request: 12442
+author: Huang Tao
diff --git a/changelogs/unreleased/34309-drop-gfm-mr-ms.yml b/changelogs/unreleased/34309-drop-gfm-mr-ms.yml
new file mode 100644
index 00000000000..07fe79e90ee
--- /dev/null
+++ b/changelogs/unreleased/34309-drop-gfm-mr-ms.yml
@@ -0,0 +1,4 @@
+---
+title: Drop GFM support for the title of Milestone/MergeRequest in template
+merge_request: 12451
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/dm-requirements-txt-tilde.yml b/changelogs/unreleased/dm-requirements-txt-tilde.yml
deleted file mode 100644
index ddb5325ddd5..00000000000
--- a/changelogs/unreleased/dm-requirements-txt-tilde.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't match tilde and exclamation mark as part of requirements.txt package
- name
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-33991.yml b/changelogs/unreleased/fix-33991.yml
new file mode 100644
index 00000000000..39732611b6e
--- /dev/null
+++ b/changelogs/unreleased/fix-33991.yml
@@ -0,0 +1,4 @@
+---
+title: Users can subscribe to group labels on the group labels page
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-34019.yml b/changelogs/unreleased/fix-34019.yml
deleted file mode 100644
index 77ebb18fda6..00000000000
--- a/changelogs/unreleased/fix-34019.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Perform project housekeeping after importing projects
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue-inline-edit-quick-submit.yml b/changelogs/unreleased/issue-inline-edit-quick-submit.yml
deleted file mode 100644
index 4407bae3e6f..00000000000
--- a/changelogs/unreleased/issue-inline-edit-quick-submit.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed ctrl+enter not submit issue edit form
-merge_request:
-author:
diff --git a/changelogs/unreleased/mk-fix-breadcrumb-order-33938.yml b/changelogs/unreleased/mk-fix-breadcrumb-order-33938.yml
deleted file mode 100644
index 790af692b92..00000000000
--- a/changelogs/unreleased/mk-fix-breadcrumb-order-33938.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix reversed breadcrumb order for nested groups
-merge_request: 12322
-author:
diff --git a/changelogs/unreleased/mk-fix-issue-34068.yml b/changelogs/unreleased/mk-fix-issue-34068.yml
deleted file mode 100644
index af0a9139568..00000000000
--- a/changelogs/unreleased/mk-fix-issue-34068.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix 500 when failing to create private group
-merge_request: 12394
-author:
diff --git a/changelogs/unreleased/pat-alert-when-signin-disabled.yml b/changelogs/unreleased/pat-alert-when-signin-disabled.yml
new file mode 100644
index 00000000000..dca3670aeb7
--- /dev/null
+++ b/changelogs/unreleased/pat-alert-when-signin-disabled.yml
@@ -0,0 +1,4 @@
+---
+title: Provide hint to create a personal access token for Git over HTTP
+merge_request: 12105
+author: Robin Bobbitt
diff --git a/changelogs/unreleased/polish-sidebar-toggle.yml b/changelogs/unreleased/polish-sidebar-toggle.yml
new file mode 100644
index 00000000000..41ec567fc52
--- /dev/null
+++ b/changelogs/unreleased/polish-sidebar-toggle.yml
@@ -0,0 +1,4 @@
+---
+title: Remove unused space in sidebar todo toggle when not signed in
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml b/changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml
new file mode 100644
index 00000000000..9e3c3e19bea
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-premailer-gem-for-filesystem.yml
@@ -0,0 +1,5 @@
+---
+title: Bump premailer-rails gem to 1.9.7 and its dependencies to prevent network retrieval
+ of assets
+merge_request:
+author:
diff --git a/changelogs/unreleased/update_bootsnap_1-1-1.yml b/changelogs/unreleased/update_bootsnap_1-1-1.yml
new file mode 100644
index 00000000000..9ecfe4b60c8
--- /dev/null
+++ b/changelogs/unreleased/update_bootsnap_1-1-1.yml
@@ -0,0 +1,4 @@
+---
+title: Bump bootsnap to 1.1.1
+merge_request: 12425
+author: @blackst0ne
diff --git a/config/application.rb b/config/application.rb
index 8bbecf3ed0f..12242c3b0f5 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -109,6 +109,7 @@ module Gitlab
config.assets.precompile << "lib/ace.js"
config.assets.precompile << "vendor/assets/fonts/*"
config.assets.precompile << "test.css"
+ config.assets.precompile << "new_nav.css"
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
diff --git a/config/boot.rb b/config/boot.rb
index 2d01092acd5..02baeab29ab 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -5,6 +5,12 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
+begin
+ require 'bootsnap/setup'
+rescue SystemCallError => exception
+ $stderr.puts "WARNING: Bootsnap failed to setup: #{exception.message}"
+end
+
# set default directory for multiproces metrics gathering
if ENV['RAILS_ENV'] == 'development' || ENV['RAILS_ENV'] == 'test'
ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir'
diff --git a/db/migrate/20170622162730_add_ref_fetched_to_merge_request.rb b/db/migrate/20170622162730_add_ref_fetched_to_merge_request.rb
new file mode 100644
index 00000000000..62aa1a4b4f0
--- /dev/null
+++ b/db/migrate/20170622162730_add_ref_fetched_to_merge_request.rb
@@ -0,0 +1,9 @@
+class AddRefFetchedToMergeRequest < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :merge_requests, :ref_fetched, :boolean
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 028556bdccf..8c7440ee610 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170621102400) do
+ActiveRecord::Schema.define(version: 20170622162730) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -770,6 +770,7 @@ ActiveRecord::Schema.define(version: 20170621102400) do
t.datetime "last_edited_at"
t.integer "last_edited_by_id"
t.integer "head_pipeline_id"
+ t.boolean "ref_fetched"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 0517ed3ec18..023c6932e41 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -736,7 +736,7 @@ X-Gitlab-Event: Merge Request Hook
### Wiki Page events
-Triggered when a wiki page is created, edited or deleted.
+Triggered when a wiki page is created, updated or deleted.
**Request Header**:
diff --git a/features/dashboard/merge_requests.feature b/features/dashboard/merge_requests.feature
deleted file mode 100644
index 4a2c997d707..00000000000
--- a/features/dashboard/merge_requests.feature
+++ /dev/null
@@ -1,21 +0,0 @@
-@dashboard
-Feature: Dashboard Merge Requests
- Background:
- Given I sign in as a user
- And I have authored merge requests
- And I have assigned merge requests
- And I have other merge requests
- And I visit dashboard merge requests page
-
- Scenario: I should see assigned merge_requests
- Then I should see merge requests assigned to me
-
- @javascript
- Scenario: I should see authored merge_requests
- When I click "Authored by me" link
- Then I should see merge requests authored by me
-
- @javascript
- Scenario: I should see all merge_requests
- When I click "All" link
- Then I should see all merge requests
diff --git a/features/dashboard/todos.feature b/features/dashboard/todos.feature
deleted file mode 100644
index 0b23bbb7951..00000000000
--- a/features/dashboard/todos.feature
+++ /dev/null
@@ -1,28 +0,0 @@
-@dashboard
-Feature: Dashboard Todos
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And "John Doe" is a developer of project "Shop"
- And "Mary Jane" is a developer of project "Shop"
- And "Mary Jane" owns private project "Enterprise"
- And I am a developer of project "Enterprise"
- And I have todos
- And I visit dashboard todos page
-
- @javascript
- Scenario: I mark todos as done
- Then I should see todos assigned to me
- And I mark the todo as done
- Then I should see the todo marked as done
-
- @javascript
- Scenario: I mark all todos as done
- Then I should see todos assigned to me
- And I mark all todos as done
- Then I should see all todos marked as done
-
- @javascript
- Scenario: I click on a todo row
- Given I click on the todo
- Then I should be directed to the corresponding page
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
deleted file mode 100644
index 909ffec3646..00000000000
--- a/features/steps/dashboard/merge_requests.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include Select2Helper
-
- step 'I should see merge requests assigned to me' do
- should_see(assigned_merge_request)
- should_see(assigned_merge_request_from_fork)
- should_not_see(authored_merge_request)
- should_not_see(authored_merge_request_from_fork)
- should_not_see(other_merge_request)
- end
-
- step 'I should see merge requests authored by me' do
- should_see(authored_merge_request)
- should_see(authored_merge_request_from_fork)
- should_not_see(assigned_merge_request)
- should_not_see(assigned_merge_request_from_fork)
- should_not_see(other_merge_request)
- end
-
- step 'I should see all merge requests' do
- should_see(authored_merge_request)
- should_see(assigned_merge_request)
- should_see(other_merge_request)
- end
-
- step 'I have authored merge requests' do
- authored_merge_request
- authored_merge_request_from_fork
- end
-
- step 'I have assigned merge requests' do
- assigned_merge_request
- assigned_merge_request_from_fork
- end
-
- step 'I have other merge requests' do
- other_merge_request
- end
-
- step 'I click "Authored by me" link' do
- find("#assignee_id").set("")
- find(".js-author-search", match: :first).click
- find(".dropdown-menu-author li a", match: :first, text: current_user.to_reference).click
- end
-
- step 'I click "All" link' do
- find(".js-author-search").click
- expect(page).to have_selector(".dropdown-menu-author li a")
- find(".dropdown-menu-author li a", match: :first).click
- expect(page).not_to have_selector(".dropdown-menu-author li a")
-
- find(".js-assignee-search").click
- expect(page).to have_selector(".dropdown-menu-assignee li a")
- find(".dropdown-menu-assignee li a", match: :first).click
- expect(page).not_to have_selector(".dropdown-menu-assignee li a")
- end
-
- def should_see(merge_request)
- expect(page).to have_content(merge_request.title[0..10])
- end
-
- def should_not_see(merge_request)
- expect(page).not_to have_content(merge_request.title[0..10])
- end
-
- def assigned_merge_request
- @assigned_merge_request ||= create :merge_request,
- assignee: current_user,
- target_project: project,
- source_project: project
- end
-
- def authored_merge_request
- @authored_merge_request ||= create :merge_request,
- source_branch: 'markdown',
- author: current_user,
- target_project: project,
- source_project: project
- end
-
- def other_merge_request
- @other_merge_request ||= create :merge_request,
- source_branch: 'fix',
- target_project: project,
- source_project: project
- end
-
- def authored_merge_request_from_fork
- @authored_merge_request_from_fork ||= create :merge_request,
- source_branch: 'feature_conflict',
- author: current_user,
- target_project: public_project,
- source_project: forked_project
- end
-
- def assigned_merge_request_from_fork
- @assigned_merge_request_from_fork ||= create :merge_request,
- source_branch: 'markdown',
- assignee: current_user,
- target_project: public_project,
- source_project: forked_project
- end
-
- def project
- @project ||= begin
- project = create(:project, :repository)
- project.team << [current_user, :master]
- project
- end
- end
-
- def public_project
- @public_project ||= create(:project, :public, :repository)
- end
-
- def forked_project
- @forked_project ||= Projects::ForkService.new(public_project, current_user).execute
- end
-end
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
deleted file mode 100644
index 4a33babe3bd..00000000000
--- a/features/steps/dashboard/todos.rb
+++ /dev/null
@@ -1,191 +0,0 @@
-class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
- include SharedUser
- include WaitForRequests
-
- step '"John Doe" is a developer of project "Shop"' do
- project.team << [john_doe, :developer]
- end
-
- step 'I am a developer of project "Enterprise"' do
- enterprise.team << [current_user, :developer]
- end
-
- step '"Mary Jane" is a developer of project "Shop"' do
- project.team << [john_doe, :developer]
- end
-
- step 'I have todos' do
- create(:todo, user: current_user, project: project, author: mary_jane, target: issue, action: Todo::MENTIONED)
- create(:todo, user: current_user, project: project, author: john_doe, target: issue, action: Todo::ASSIGNED)
- note = create(:note, author: john_doe, noteable: issue, note: "#{current_user.to_reference} Wdyt?", project: project)
- create(:todo, user: current_user, project: project, author: john_doe, target: issue, action: Todo::MENTIONED, note: note)
- create(:todo, user: current_user, project: project, author: john_doe, target: merge_request, action: Todo::ASSIGNED)
- end
-
- step 'I should see todos assigned to me' do
- merge_request_reference = merge_request.to_reference(full: true)
- issue_reference = issue.to_reference(full: true)
-
- page.within('.todos-count') { expect(page).to have_content '4' }
- expect(page).to have_content 'To do 4'
- expect(page).to have_content 'Done 0'
-
- expect(page).to have_link project.name_with_namespace
- should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title)
- should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?")
- should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title)
- should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title)
- end
-
- step 'I mark the todo as done' do
- page.within('.todo:nth-child(1)') do
- click_link 'Done'
- end
-
- page.within('.todos-count') { expect(page).to have_content '3' }
- expect(page).to have_content 'To do 3'
- expect(page).to have_content 'Done 1'
- should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, state: :done_reversible)
- end
-
- step 'I mark all todos as done' do
- merge_request_reference = merge_request.to_reference(full: true)
- issue_reference = issue.to_reference(full: true)
-
- find('.js-todos-mark-all').trigger('click')
-
- page.within('.todos-count') { expect(page).to have_content '0' }
- expect(page).to have_content 'To do 0'
- expect(page).to have_content 'Done 4'
- expect(page).to have_content "You're all done!"
- expect('.prepend-top-default').not_to have_link project.name_with_namespace
- should_not_see_todo "John Doe assigned you merge request #{merge_request_reference}"
- should_not_see_todo "John Doe mentioned you on issue #{issue_reference}"
- should_not_see_todo "John Doe assigned you issue #{issue_reference}"
- should_not_see_todo "Mary Jane mentioned you on issue #{issue_reference}"
- end
-
- step 'I should see the todo marked as done' do
- find('.todos-done a').trigger('click')
-
- expect(page).to have_link project.name_with_namespace
- should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, state: :done_irreversible)
- end
-
- step 'I should see all todos marked as done' do
- merge_request_reference = merge_request.to_reference(full: true)
- issue_reference = issue.to_reference(full: true)
-
- find('.todos-done a').trigger('click')
-
- expect(page).to have_link project.name_with_namespace
- should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title, state: :done_irreversible)
- should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?", state: :done_irreversible)
- should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title, state: :done_irreversible)
- should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title, state: :done_irreversible)
- end
-
- step 'I filter by "Enterprise"' do
- click_button 'Project'
- page.within '.dropdown-menu-project' do
- click_link enterprise.name_with_namespace
- end
- end
-
- step 'I filter by "John Doe"' do
- click_button 'Author'
- page.within '.dropdown-menu-author' do
- click_link john_doe.username
- end
- end
-
- step 'I filter by "Issue"' do
- click_button 'Type'
- page.within '.dropdown-menu-type' do
- click_link 'Issue'
- end
- end
-
- step 'I filter by "Mentioned"' do
- click_button 'Action'
- page.within '.dropdown-menu-action' do
- click_link 'Mentioned'
- end
- end
-
- step 'I should not see todos' do
- expect(page).to have_content "You're all done!"
- end
-
- step 'I should not see todos related to "Mary Jane" in the list' do
- should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference(full: true)}"
- end
-
- step 'I should not see todos related to "Merge Requests" in the list' do
- should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}"
- end
-
- step 'I should not see todos related to "Assignments" in the list' do
- should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}"
- should_not_see_todo "John Doe assigned you issue #{issue.to_reference(full: true)}"
- end
-
- step 'I click on the todo' do
- find('.todo:nth-child(1)').click
- end
-
- step 'I should be directed to the corresponding page' do
- page.should have_css('.identifier', text: 'Merge request !1')
- # Merge request page loads and issues a number of Ajax requests
- wait_for_requests
- end
-
- def should_see_todo(position, title, body, state: :pending)
- page.within(".todo:nth-child(#{position})") do
- expect(page).to have_content title
- expect(page).to have_content body
-
- if state == :pending
- expect(page).to have_link 'Done'
- elsif state == :done_reversible
- expect(page).to have_link 'Undo'
- elsif state == :done_irreversible
- expect(page).not_to have_link 'Undo'
- expect(page).not_to have_link 'Done'
- else
- raise 'Invalid state given, valid states: :pending, :done_reversible, :done_irreversible'
- end
- end
- end
-
- def should_not_see_todo(title)
- expect(page).not_to have_visible_content title
- end
-
- def have_visible_content(text)
- have_css('*', text: text, visible: true)
- end
-
- def john_doe
- @john_doe ||= user_exists("John Doe", { username: "john_doe" })
- end
-
- def mary_jane
- @mary_jane ||= user_exists("Mary Jane", { username: "mary_jane" })
- end
-
- def enterprise
- @enterprise ||= Project.find_by(name: 'Enterprise')
- end
-
- def issue
- @issue ||= create(:issue, assignees: [current_user], project: project)
- end
-
- def merge_request
- @merge_request ||= create(:merge_request, assignee: current_user, source_project: project)
- end
-end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index f0e751b820a..8a5b4112ffe 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -112,10 +112,6 @@ module SharedPaths
visit dashboard_groups_path
end
- step 'I visit dashboard todos page' do
- visit dashboard_todos_path
- end
-
step 'I should be redirected to the dashboard groups page' do
expect(current_path).to eq dashboard_groups_path
end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 381c4ef50b0..10374995497 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -45,7 +45,7 @@ module API
optional :protected, type: String, desc: 'Whether the variable is protected'
end
post ':id/variables' do
- variable = user_project.variables.create(declared(params, include_parent_namespaces: false).to_h)
+ variable = user_project.variables.create(declared_params(include_missing: false))
if variable.valid?
present variable, with: Entities::Variable
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 6d326ee213a..1a5887dab7e 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -76,9 +76,13 @@ module Gitlab
step(
"Generating the patch against origin/master in #{patch_path}",
- %W[git diff --binary origin/master > #{patch_path}]
+ %w[git diff --binary origin/master...HEAD]
) do |output, status|
- throw(:halt_check, :ko) unless status.zero? && File.exist?(patch_path)
+ throw(:halt_check, :ko) unless status.zero?
+
+ File.write(patch_path, output)
+
+ throw(:halt_check, :ko) unless File.exist?(patch_path)
end
end
@@ -130,7 +134,15 @@ module Gitlab
step("Fetching CE/#{ce_branch}", %W[git fetch #{CE_REPO} #{ce_branch}])
step(
"Checking if #{patch_path} applies cleanly to EE/master",
- %W[git apply --check --3way #{patch_path}]
+ # Don't use --check here because it can result in a 0-exit status even
+ # though the patch doesn't apply cleanly, e.g.:
+ # > git apply --check --3way foo.patch
+ # error: patch failed: lib/gitlab/ee_compat_check.rb:74
+ # Falling back to three-way merge...
+ # Applied patch to 'lib/gitlab/ee_compat_check.rb' with conflicts.
+ # > echo $?
+ # 0
+ %W[git apply --3way #{patch_path}]
) do |output, status|
puts output
unless status.zero?
@@ -145,6 +157,7 @@ module Gitlab
status = 0 if failed_files.empty?
end
+ command(%w[git reset --hard])
status
end
end
@@ -292,7 +305,7 @@ module Gitlab
# In the CE repo
$ git fetch origin master
- $ git diff --binary origin/master > #{ce_branch}.patch
+ $ git diff --binary origin/master...HEAD -- > #{ce_branch}.patch
# In the EE repo
$ git fetch origin master
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index 62ddd45785d..a0f46594eb1 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -10,13 +10,21 @@ module Gitlab
# ExclusiveLease.
#
class ExclusiveLease
- LUA_CANCEL_SCRIPT = <<-EOS.freeze
+ LUA_CANCEL_SCRIPT = <<~EOS.freeze
local key, uuid = KEYS[1], ARGV[1]
if redis.call("get", key) == uuid then
redis.call("del", key)
end
EOS
+ LUA_RENEW_SCRIPT = <<~EOS.freeze
+ local key, uuid, ttl = KEYS[1], ARGV[1], ARGV[2]
+ if redis.call("get", key) == uuid then
+ redis.call("expire", key, ttl)
+ return uuid
+ end
+ EOS
+
def self.cancel(key, uuid)
Gitlab::Redis.with do |redis|
redis.eval(LUA_CANCEL_SCRIPT, keys: [redis_key(key)], argv: [uuid])
@@ -42,6 +50,15 @@ module Gitlab
end
end
+ # Try to renew an existing lease. Return lease UUID on success,
+ # false if the lease is taken by a different UUID or inexistent.
+ def renew
+ Gitlab::Redis.with do |redis|
+ result = redis.eval(LUA_RENEW_SCRIPT, keys: [@redis_key], argv: [@uuid, @timeout])
+ result == @uuid
+ end
+ end
+
# Returns true if the key for this lease is set.
def exists?
Gitlab::Redis.with do |redis|
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index d5d149f1423..b68378f5c0b 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -175,8 +175,8 @@ module Gitlab
# Shows the diff between the commit's parent and the commit.
#
# Cuts out the header and stats from #to_patch and returns only the diff.
- def to_diff(options = {})
- diff_from_parent(options).patch
+ def to_diff
+ diff_from_parent.patch
end
# Returns a diff object for the changes from this commit's first parent.
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index a5ad2f952d3..db7cdf4b5c7 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -11,7 +11,8 @@ module Gitlab
'zh_CN' => '简体中文',
'zh_HK' => '繁體中文(香港)',
'zh_TW' => '繁體中文(臺灣)',
- 'bg' => 'български'
+ 'bg' => 'български',
+ 'eo' => 'Esperanto'
}.freeze
def available_locales
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 72183e8aad4..1860352c96d 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -99,6 +99,7 @@ excluded_attributes:
- :milestone_id
merge_requests:
- :milestone_id
+ - :ref_fetched
award_emoji:
- :awardable_id
statuses:
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
new file mode 100644
index 00000000000..3ef9e19067d
--- /dev/null
+++ b/locale/eo/gitlab.po
@@ -0,0 +1,1143 @@
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Lyubomir Vasilev <lyubomirv@abv.bg>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-15 21:59-0500\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-06-20 06:24-0400\n"
+"Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
+"Language-Team: Esperanto (https://translate.zanata.org/project/view/GitLab)\n"
+"Language: eo\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} enmetis %{commit_timeago}"
+
+msgid "About auto deploy"
+msgstr "Pri la aŭtomata disponigado"
+
+msgid "Active"
+msgstr "Aktiva"
+
+msgid "Activity"
+msgstr "Aktiveco"
+
+msgid "Add Changelog"
+msgstr "Aldoni liston de ŝanĝoj"
+
+msgid "Add Contribution guide"
+msgstr "Aldoni gvidliniojn por kontribuado"
+
+msgid "Add License"
+msgstr "Aldoni rajtigilon"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+"Aldonu SSH-ŝlosilon al via profilo por ebligi al vi eltiri kaj alpuŝi per "
+"SSH."
+
+msgid "Add new directory"
+msgstr "Aldoni novan dosierujon"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Arkivita projekto! La deponejo permesas nur legadon"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Ĉu vi certe volas forigi ĉi tiun ĉenstablan planon?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "Alkroĉu dosieron per ŝovmetado aŭ %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Branĉo"
+msgstr[1] "Branĉoj"
+
+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}"
+msgstr ""
+"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}"
+
+msgid "Branches"
+msgstr "Branĉoj"
+
+msgid "Browse files"
+msgstr "Elekti dosierojn"
+
+msgid "ByAuthor|by"
+msgstr "de"
+
+msgid "CI configuration"
+msgstr "Agordoj de seninterrompa integrado"
+
+msgid "Cancel"
+msgstr "Nuligi"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Elekti en branĉon"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "Malfari en branĉo"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Precize elekti"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Malfari"
+
+msgid "Changelog"
+msgstr "Listo de ŝanĝoj"
+
+msgid "Charts"
+msgstr "Diagramoj"
+
+msgid "Cherry-pick this commit"
+msgstr "Precize elekti ĉi tiun kunmetadon"
+
+msgid "Cherry-pick this merge request"
+msgstr "Precize elekti ĉi tiun peton pri kunfando"
+
+msgid "CiStatusLabel|canceled"
+msgstr "nuligita"
+
+msgid "CiStatusLabel|created"
+msgstr "kreita"
+
+msgid "CiStatusLabel|failed"
+msgstr "malsukcesa"
+
+msgid "CiStatusLabel|manual action"
+msgstr "mana ago"
+
+msgid "CiStatusLabel|passed"
+msgstr "sukcesa"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "sukcesa, kun avertoj"
+
+msgid "CiStatusLabel|pending"
+msgstr "okazonta"
+
+msgid "CiStatusLabel|skipped"
+msgstr "transsaltita"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "atendanta manan agon"
+
+msgid "CiStatusText|blocked"
+msgstr "blokita"
+
+msgid "CiStatusText|canceled"
+msgstr "nuligita"
+
+msgid "CiStatusText|created"
+msgstr "kreita"
+
+msgid "CiStatusText|failed"
+msgstr "malsukcesa"
+
+msgid "CiStatusText|manual"
+msgstr "mana"
+
+msgid "CiStatusText|passed"
+msgstr "sukcesa"
+
+msgid "CiStatusText|pending"
+msgstr "okazonta"
+
+msgid "CiStatusText|skipped"
+msgstr "transsaltita"
+
+msgid "CiStatus|running"
+msgstr "plenumiĝanta"
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] "Enmetado"
+msgstr[1] "Enmetadoj"
+
+msgid "Commit message"
+msgstr "Mesaĝo pri la enmetado"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Enmeti"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "Aldoni „%{file_name}“"
+
+msgid "Commits"
+msgstr "Enmetadoj"
+
+msgid "Commits|History"
+msgstr "Historio"
+
+msgid "Committed by"
+msgstr "Enmetita de"
+
+msgid "Compare"
+msgstr "Kompari"
+
+msgid "Contribution guide"
+msgstr "Gvidlinioj por kontribuado"
+
+msgid "Contributors"
+msgstr "Kontribuantoj"
+
+msgid "Copy URL to clipboard"
+msgstr "Kopii la adreson en la kopibufron"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Kopii la identigilon de la enmetado"
+
+msgid "Create New Directory"
+msgstr "Krei novan dosierujon"
+
+msgid "Create directory"
+msgstr "Krei dosierujon"
+
+msgid "Create empty bare repository"
+msgstr "Krei malplenan deponejon"
+
+msgid "Create merge request"
+msgstr "Krei peton pri kunfando"
+
+msgid "Create new..."
+msgstr "Krei novan…"
+
+msgid "CreateNewFork|Fork"
+msgstr "Disbranĉigi"
+
+msgid "CreateTag|Tag"
+msgstr "Etikedo"
+
+msgid "Cron Timezone"
+msgstr "Horzono por Cron"
+
+msgid "Cron syntax"
+msgstr "La sintakso de Cron"
+
+msgid "Custom notification events"
+msgstr "Propraj sciigaj eventoj"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
+msgstr ""
+"La propraj sciigaj niveloj estas la samaj kiel la niveloj de partoprenado. "
+"Uzante la proprajn sciigajn nivelojn, vi ricevos ankaŭ sciigojn por "
+"elektitaj de vi eventoj. Por lerni pli, bonvolu vidi %{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Cikla analizo"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr ""
+"La cikla analizo esploras kiom da tempo necesas por disvolvi ideon ĝis ĝi "
+"fariĝos realaĵo."
+
+msgid "CycleAnalyticsStage|Code"
+msgstr "Programado"
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr "Problemo"
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr "Plano"
+
+msgid "CycleAnalyticsStage|Production"
+msgstr "Eldonado"
+
+msgid "CycleAnalyticsStage|Review"
+msgstr "Kontrolo"
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr "Preparo por eldono"
+
+msgid "CycleAnalyticsStage|Test"
+msgstr "Testado"
+
+msgid "Define a custom pattern with cron syntax"
+msgstr "Difini propran ŝablonon, uzante la sintakson de Cron"
+
+msgid "Delete"
+msgstr "Forigi"
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] "Disponigado"
+msgstr[1] "Disponigadoj"
+
+msgid "Description"
+msgstr "Priskribo"
+
+msgid "Directory name"
+msgstr "Nomo de dosierujo"
+
+msgid "Don't show again"
+msgstr "Ne montru denove"
+
+msgid "Download"
+msgstr "Elŝuti"
+
+msgid "Download tar"
+msgstr "Elŝuti en formato „tar“"
+
+msgid "Download tar.bz2"
+msgstr "Elŝuti en formato „tar.bz2“"
+
+msgid "Download tar.gz"
+msgstr "Elŝuti en formato „tar.gz“"
+
+msgid "Download zip"
+msgstr "Elŝuti en formato „zip“"
+
+msgid "DownloadArtifacts|Download"
+msgstr "Elŝuti"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "Sendi flikaĵojn per retpoŝto"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Normala dosiero kun diferencoj"
+
+msgid "DownloadSource|Download"
+msgstr "Elŝuti"
+
+msgid "Edit"
+msgstr "Redakti"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Redakti ĉenstablan planon %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Ĉiutage (je 4:00)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Ĉiumonate (en la 1a de la monato, je 4:00)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Ĉiusemajne (en dimanĉo, je 4:00)"
+
+msgid "Failed to change the owner"
+msgstr "Ne eblas ŝanĝi la posedanton"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Ne eblas forigi la ĉenstablan planon"
+
+msgid "Files"
+msgstr "Dosieroj"
+
+msgid "Find by path"
+msgstr "Trovi per dosierindiko"
+
+msgid "Find file"
+msgstr "Trovi dosieron"
+
+msgid "FirstPushedBy|First"
+msgstr "Unue"
+
+msgid "FirstPushedBy|pushed by"
+msgstr "alpuŝita de"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Disbranĉigo"
+msgstr[1] "Disbranĉigoj"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "Disbranĉigita el"
+
+msgid "From issue creation until deploy to production"
+msgstr "De la kreado de la problemo ĝis la disponigado en la publika versio"
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+"De la kunfandado de la peto pri kunfando ĝis la disponigado en la publika "
+"versio"
+
+msgid "Go to your fork"
+msgstr "Al via disbranĉigo"
+
+msgid "GoToYourFork|Fork"
+msgstr "Disbranĉigo"
+
+msgid "Home"
+msgstr "Hejmo"
+
+msgid "Housekeeping successfully started"
+msgstr "La refreŝigo komenciĝis sukcese"
+
+msgid "Import repository"
+msgstr "Enporti deponejon"
+
+msgid "Interval Pattern"
+msgstr "Intervala ŝablono"
+
+msgid "Introducing Cycle Analytics"
+msgstr "Ni prezentas al vi la ciklan analizon"
+
+msgid "LFSStatus|Disabled"
+msgstr "Malŝaltita"
+
+msgid "LFSStatus|Enabled"
+msgstr "Ŝaltita"
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] "La lasta %d tago"
+msgstr[1] "La lastaj %d tagoj"
+
+msgid "Last Pipeline"
+msgstr "Lasta ĉenstablo"
+
+msgid "Last Update"
+msgstr "Lasta ĝisdatigo"
+
+msgid "Last commit"
+msgstr "Lasta enmetado"
+
+msgid "Learn more in the"
+msgstr "Lernu pli en la"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "dokumentado pri ĉenstablaj planoj"
+
+msgid "Leave group"
+msgstr "Forlasi la grupon"
+
+msgid "Leave project"
+msgstr "Forlasi la projekton"
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] "Limigita al montrado de ne pli ol %d evento"
+msgstr[1] "Limigita al montrado de ne pli ol %d eventoj"
+
+msgid "Median"
+msgstr "Mediano"
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "aldonos SSH-ŝlosilon"
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] "Nova problemo"
+msgstr[1] "Novaj problemoj"
+
+msgid "New Pipeline Schedule"
+msgstr "Nova ĉenstabla plano"
+
+msgid "New branch"
+msgstr "Nova branĉo"
+
+msgid "New directory"
+msgstr "Nova dosierujo"
+
+msgid "New file"
+msgstr "Nova dosiero"
+
+msgid "New issue"
+msgstr "Nova problemo"
+
+msgid "New merge request"
+msgstr "Nova peto pri kunfando"
+
+msgid "New schedule"
+msgstr "Nova plano"
+
+msgid "New snippet"
+msgstr "Nova kodaĵo"
+
+msgid "New tag"
+msgstr "Nova etikedo"
+
+msgid "No repository"
+msgstr "Ne estas deponejo"
+
+msgid "No schedules"
+msgstr "Ne estas planoj"
+
+msgid "Not available"
+msgstr "Ne disponebla"
+
+msgid "Not enough data"
+msgstr "Ne estas sufiĉe da datenoj"
+
+msgid "Notification events"
+msgstr "Sciigaj eventoj"
+
+msgid "NotificationEvent|Close issue"
+msgstr "Fermi problemon"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Fermi peton pri kunfando"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "Malsukcesa ĉenstablo"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "Apliki peton pri kunfando"
+
+msgid "NotificationEvent|New issue"
+msgstr "Nova problemo"
+
+msgid "NotificationEvent|New merge request"
+msgstr "Nova peto pri kunfando"
+
+msgid "NotificationEvent|New note"
+msgstr "Nova noto"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "Reatribui problemon"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "Reatribui peton pri kunfando"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "Remalfermi problemon"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "Sukcesa ĉenstablo"
+
+msgid "NotificationLevel|Custom"
+msgstr "Propraj"
+
+msgid "NotificationLevel|Disabled"
+msgstr "Malŝaltitaj"
+
+msgid "NotificationLevel|Global"
+msgstr "Ĝeneralaj"
+
+msgid "NotificationLevel|On mention"
+msgstr "Ĉe mencio"
+
+msgid "NotificationLevel|Participate"
+msgstr "Partoprenado"
+
+msgid "NotificationLevel|Watch"
+msgstr "Rigardado"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "Filtrilo"
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr "Malfermita"
+
+msgid "Options"
+msgstr "Opcioj"
+
+msgid "Owner"
+msgstr "Posedanto"
+
+msgid "Pipeline"
+msgstr "Ĉenstablo"
+
+msgid "Pipeline Health"
+msgstr "Stato"
+
+msgid "Pipeline Schedule"
+msgstr "Ĉenstabla plano"
+
+msgid "Pipeline Schedules"
+msgstr "Ĉenstablaj planoj"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Ŝaltita"
+
+msgid "PipelineSchedules|Active"
+msgstr "Ŝaltitaj"
+
+msgid "PipelineSchedules|All"
+msgstr "Ĉiuj"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Malŝaltitaj"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "Sekvanta plenumo"
+
+msgid "PipelineSchedules|None"
+msgstr "Nenio"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Entajpu mallongan priskribon pri ĉi tiu ĉenstablo"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "Akiri posedon"
+
+msgid "PipelineSchedules|Target"
+msgstr "Celo"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "Propra"
+
+msgid "Pipeline|with stage"
+msgstr "kun etapo"
+
+msgid "Pipeline|with stages"
+msgstr "kun etapoj"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "La projekto „%{project_name}“ estis alvicigita por forigado."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "La projekto „%{project_name}“ estis sukcese kreita."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "La projekto „%{project_name}“ estis sukcese ĝisdatigita."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "La projekto „%{project_name}“ estos forigita."
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "Ĉiu uzanto devas akiri propran atingon al la projekto."
+
+msgid "Project export could not be deleted."
+msgstr "Ne eblas forigi la projektan elporton."
+
+msgid "Project export has been deleted."
+msgstr "La projekta elporto estis forigita."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"La ligilo por la projekta elporto eksvalidiĝis. Bonvolu krei novan elporton "
+"en la agordoj de la projekto."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"La elporto de la projekto komenciĝis. Vi ricevos ligilon per retpoŝto por "
+"elŝuti la datenoj."
+
+msgid "Project home"
+msgstr "Hejmo de la projekto"
+
+msgid "ProjectFeature|Disabled"
+msgstr "Malŝaltita"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "Ĉiu, kiu havas atingon"
+
+msgid "ProjectFeature|Only team members"
+msgstr "Nur skipanoj"
+
+msgid "ProjectFileTree|Name"
+msgstr "Nomo"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Neniam"
+
+msgid "ProjectLifecycle|Stage"
+msgstr "Etapo"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "Grafeo"
+
+msgid "Read more"
+msgstr "Legu pli"
+
+msgid "Readme"
+msgstr "LeguMin"
+
+msgid "RefSwitcher|Branches"
+msgstr "Branĉoj"
+
+msgid "RefSwitcher|Tags"
+msgstr "Etikedoj"
+
+msgid "Related Commits"
+msgstr "Rilataj enmetadoj"
+
+msgid "Related Deployed Jobs"
+msgstr "Rilataj disponigitaj taskoj"
+
+msgid "Related Issues"
+msgstr "Rilataj problemoj"
+
+msgid "Related Jobs"
+msgstr "Rilataj taskoj"
+
+msgid "Related Merge Requests"
+msgstr "Rilataj petoj pri kunfando"
+
+msgid "Related Merged Requests"
+msgstr "Rilataj aplikitaj petoj pri kunfando"
+
+msgid "Remind later"
+msgstr "Rememorigu denove"
+
+msgid "Remove project"
+msgstr "Forigi la projekton"
+
+msgid "Request Access"
+msgstr "Peti atingeblon"
+
+msgid "Revert this commit"
+msgstr "Malfari ĉi tiun enmetadon"
+
+msgid "Revert this merge request"
+msgstr "Malfari ĉi tiun peton pri kunfando"
+
+msgid "Save pipeline schedule"
+msgstr "Konservi ĉenstablan planon"
+
+msgid "Schedule a new pipeline"
+msgstr "Plani novan ĉenstablon"
+
+msgid "Scheduling Pipelines"
+msgstr "Planado de la ĉenstabloj"
+
+msgid "Search branches and tags"
+msgstr "Serĉu branĉon aŭ etikedon"
+
+msgid "Select Archive Format"
+msgstr "Elektu formaton de arkivo"
+
+msgid "Select a timezone"
+msgstr "Elektu horzonon"
+
+msgid "Select target branch"
+msgstr "Elektu celan branĉon"
+
+msgid "Set a password on your account to pull or push via %{protocol}"
+msgstr ""
+"Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuŝi per "
+"%{protocol}"
+
+msgid "Set up CI"
+msgstr "Agordi SI"
+
+msgid "Set up Koding"
+msgstr "Agordi „Koding“"
+
+msgid "Set up auto deploy"
+msgstr "Agordi aŭtomatan disponigadon"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "kreos pasvorton"
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] "Estas montrata %d evento"
+msgstr[1] "Estas montrataj %d eventoj"
+
+msgid "Source code"
+msgstr "Kodo"
+
+msgid "StarProject|Star"
+msgstr "Steligi"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "Kreu %{new_merge_request} kun ĉi tiuj ŝanĝoj"
+
+msgid "Switch branch/tag"
+msgstr "Iri al branĉo/etikedo"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "Etikedo"
+msgstr[1] "Etikedoj"
+
+msgid "Tags"
+msgstr "Etikedoj"
+
+msgid "Target Branch"
+msgstr "Cela branĉo"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"La etapo de programado montras la tempon de la unua enmetado ĝis la kreado "
+"de la peto pri kunfando. La datenoj aldoniĝos aŭtomate ĉi tie post kiam vi "
+"kreas la unuan peton pri kunfando."
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+"La aro da eventoj, kiuj estas aldonitaj al la datenoj kolektitaj por la "
+"etapo."
+
+msgid "The fork relationship has been removed."
+msgstr "La rilato de disbranĉigo estis forigita."
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"La etapo de la problemo montras kiom la tempo pasas de la kreado de problemo "
+"ĝis la atribuado de la problemo al cela etapo de la projekto, aŭ al listo "
+"sur la problemtabulo. Komencu krei problemojn por vidi la datenojn por ĉi "
+"tiu etapo."
+
+msgid "The phase of the development lifecycle."
+msgstr "La etapo de la disvolva ciklo."
+
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"La ĉenstabla plano plenumas ĉenstablojn en la estonteco, ripete, por "
+"difinitaj branĉoj aŭ etikedoj. Tiuj planitaj ĉenstabloj heredos la limigitan "
+"atingon al la projekto de la rilata uzanto."
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr ""
+"La etapo de la plano montras la tempon de la antaŭa ŝtupo ĝis la alpuŝado de "
+"via unua enmetado. Ĉi tiu tempo aldoniĝos aŭtomate post kiam vi alpuŝas la "
+"unuan enmetadon."
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+"La etapo de eldonado montras la tutan tempon de la kreado de problemo ĝis la "
+"disponigado en la publika versio. La datenoj aldoniĝos aŭtomate post kiam vi "
+"kompletigos plenan ciklon de ideo ĝis realaĵo."
+
+msgid "The project can be accessed by any logged in user."
+msgstr "Ĉiu ensalutita uzanto havas atingon al la projekto"
+
+msgid "The project can be accessed without any authentication."
+msgstr "Ĉiu povas havi atingon al la projekto, sen ensaluti"
+
+msgid "The repository for this project does not exist."
+msgstr "La deponejo por ĉi tiu projekto ne ekzistas."
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"La etapo de la kontrolo montras la tempon de la kreado de la peto pri "
+"kunfando ĝis ĝia aplikado. La datenoj aldoniĝos aŭtomate post kiam vi "
+"aplikos la unuan peton pri kunfando."
+
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr ""
+"La etapo de preparo por eldono montras la tempon inter la aplikado de la "
+"peto pri kunfando kaj la disponigado de la kodo en la publika versio. La "
+"datenoj aldoniĝos aŭtomate post kiam vi faros la unuan disponigadon en la "
+"publika versio."
+
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr ""
+"La etapo de testado montras kiom da tempo necesas al „GitLab CI“ por plenumi "
+"ĉiujn ĉenstablojn por la rilata peto pri kunfando. La datenoj aldoniĝos "
+"aŭtomate post kiam via unua ĉenstablo finiĝos."
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr "La tempo, kiu estas necesa por ĉiu dateno kolektita de la etapo."
+
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+"La valoro, kiu troviĝas en la mezo de aro da rigardataj valoroj. Ekzemple: "
+"inter 3, 5 kaj 9, la mediano estas 5. Inter 3, 5, 7 kaj 8, la mediano estas "
+"(5+7)/2 = 6."
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Ĉi tiu signifas, ke vi ne povos alpuŝi kodon, antaŭ ol vi kreos malplenan "
+"deponejon aŭ enportos jam ekzistantan."
+
+msgid "Time before an issue gets scheduled"
+msgstr "Tempo antaŭ problemo estas planita por ellabori"
+
+msgid "Time before an issue starts implementation"
+msgstr "Tempo antaŭ la komenco de laboro super problemo"
+
+msgid "Time between merge request creation and merge/close"
+msgstr "Tempo inter la kreado de poeto pri kunfando kaj ĝia aplikado/fermado"
+
+msgid "Time until first merge request"
+msgstr "Tempo ĝis la unua peto pri kunfando"
+
+msgid "Timeago|%s days ago"
+msgstr "antaŭ %s tagoj"
+
+msgid "Timeago|%s days remaining"
+msgstr "restas %s tagoj"
+
+msgid "Timeago|%s hours remaining"
+msgstr "restas %s horoj"
+
+msgid "Timeago|%s minutes ago"
+msgstr "antaŭ %s minutoj"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "restas %s minutoj"
+
+msgid "Timeago|%s months ago"
+msgstr "antaŭ %s monatoj"
+
+msgid "Timeago|%s months remaining"
+msgstr "restas %s monatoj"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "restas %s sekundoj"
+
+msgid "Timeago|%s weeks ago"
+msgstr "antaŭ %s semajnoj"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "restas %s semajnoj"
+
+msgid "Timeago|%s years ago"
+msgstr "antaŭ %s jaroj"
+
+msgid "Timeago|%s years remaining"
+msgstr "restas %s jaroj"
+
+msgid "Timeago|1 day remaining"
+msgstr "restas 1 tago"
+
+msgid "Timeago|1 hour remaining"
+msgstr "restas 1 horo"
+
+msgid "Timeago|1 minute remaining"
+msgstr "restas 1 minuto"
+
+msgid "Timeago|1 month remaining"
+msgstr "restas 1 monato"
+
+msgid "Timeago|1 week remaining"
+msgstr "restas 1 semajno"
+
+msgid "Timeago|1 year remaining"
+msgstr "restas 1 jaro"
+
+msgid "Timeago|Past due"
+msgstr "Malfruiĝis"
+
+msgid "Timeago|a day ago"
+msgstr "antaŭ unu tago"
+
+msgid "Timeago|a month ago"
+msgstr "antaŭ unu monato"
+
+msgid "Timeago|a week ago"
+msgstr "antaŭ unu semajno"
+
+msgid "Timeago|a while"
+msgstr "antaŭ iom da tempo"
+
+msgid "Timeago|a year ago"
+msgstr "antaŭ unu jaro"
+
+msgid "Timeago|about %s hours ago"
+msgstr "antaŭ ĉirkaŭ %s horoj"
+
+msgid "Timeago|about a minute ago"
+msgstr "antaŭ ĉirkaŭ unu minuto"
+
+msgid "Timeago|about an hour ago"
+msgstr "antaŭ ĉirkaŭ unu horo"
+
+msgid "Timeago|in %s days"
+msgstr "post %s tagoj"
+
+msgid "Timeago|in %s hours"
+msgstr "post %s horoj"
+
+msgid "Timeago|in %s minutes"
+msgstr "post %s minutoj"
+
+msgid "Timeago|in %s months"
+msgstr "post %s monatoj"
+
+msgid "Timeago|in %s seconds"
+msgstr "post %s sekundoj"
+
+msgid "Timeago|in %s weeks"
+msgstr "post %s semajnoj"
+
+msgid "Timeago|in %s years"
+msgstr "post %s jaroj"
+
+msgid "Timeago|in 1 day"
+msgstr "post 1 tago"
+
+msgid "Timeago|in 1 hour"
+msgstr "post 1 horo"
+
+msgid "Timeago|in 1 minute"
+msgstr "post 1 minuto"
+
+msgid "Timeago|in 1 month"
+msgstr "post 1 monato"
+
+msgid "Timeago|in 1 week"
+msgstr "post 1 semajno"
+
+msgid "Timeago|in 1 year"
+msgstr "post 1 jaro"
+
+msgid "Timeago|less than a minute ago"
+msgstr "antaŭ malpli ol minuto"
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] "h"
+msgstr[1] "h"
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] "min"
+msgstr[1] "min"
+
+msgid "Time|s"
+msgstr "s"
+
+msgid "Total Time"
+msgstr "Totala tempo"
+
+msgid "Total test time for all commits/merges"
+msgstr "Totala tempo por la testado de ĉiuj enmetadoj/kunfandoj"
+
+msgid "Unstar"
+msgstr "Malsteligi"
+
+msgid "Upload New File"
+msgstr "Alŝuti novan dosieron"
+
+msgid "Upload file"
+msgstr "Alŝuti dosieron"
+
+msgid "Use your global notification setting"
+msgstr "Uzi vian ĝeneralan agordon pri la sciigoj"
+
+msgid "VisibilityLevel|Internal"
+msgstr "Interna"
+
+msgid "VisibilityLevel|Private"
+msgstr "Privata"
+
+msgid "VisibilityLevel|Public"
+msgstr "Publika"
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+"Ĉu vi volas vidi la datenojn? Bonvolu peti atingeblon de administranto."
+
+msgid "We don't have enough data to show this stage."
+msgstr "Ne estas sufiĉe da datenoj por montri ĉi tiun etapon."
+
+msgid "Withdraw Access Request"
+msgstr "Nuligi la peton pri atingeblo"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vi forigos „%{project_name_with_namespace}“.\n"
+"Oni NE POVAS malfari la forigon de projekto!\n"
+"Ĉu vi estas ABSOLUTE certa?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"Vi forigos la rilaton de la disbranĉigo al la originala projekto, "
+"„%{forked_from_project}“. Ĉu vi estas ABSOLUTE certa?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vi transigos „%{project_name_with_namespace}“ al alia posedanto. Ĉu vi estas "
+"ABSOLUTE certa?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Oni povas aldoni dosierojn nur kiam oni estas en branĉo"
+
+msgid "You have reached your project limit"
+msgstr "Vi ne povas krei pliajn projektojn"
+
+msgid "You must sign in to star a project"
+msgstr "Oni devas ensaluti por steligi projekton"
+
+msgid "You need permission."
+msgstr "VI bezonas permeson."
+
+msgid "You will not get any notifications via email"
+msgstr "VI ne ricevos sciigojn per retpoŝto"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "Vi ricevos sciigojn nur por la eventoj elektitaj de vi"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "Vi ricevos sciigojn nur por la fadenoj, en kiuj vi partoprenis"
+
+msgid "You will receive notifications for any activity"
+msgstr "Vi ricevos sciigojn por ĉiu ago"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "Vi ricevos sciigojn nur por komentoj, en kiuj vi estas @menciita"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Vi ne povos eltiri aŭ alpuŝi kodon per %{protocol} antaŭ ol vi "
+"%{set_password_link} por via konto"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Vi ne povos eltiri aŭ alpuŝi kodon per SSH antaŭ ol vi %{add_ssh_key_link} "
+"al via profilo"
+
+msgid "Your name"
+msgstr "Via nomo"
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] "tago"
+msgstr[1] "tagoj"
+
+msgid "new merge request"
+msgstr "novan peton pri kunfando"
+
+msgid "notification emails"
+msgstr "sciigoj per retpoŝto"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "patro"
+msgstr[1] "patroj"
+
diff --git a/locale/eo/gitlab.po.time_stamp b/locale/eo/gitlab.po.time_stamp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/locale/eo/gitlab.po.time_stamp
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 5b3323fed13..6ad2d456b93 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -9,31 +9,54 @@ describe "Admin Runners" do
end
describe "Runners page" do
- before do
- runner = FactoryGirl.create(:ci_runner, contacted_at: Time.now)
- pipeline = FactoryGirl.create(:ci_pipeline)
- FactoryGirl.create(:ci_build, pipeline: pipeline, runner_id: runner.id)
- visit admin_runners_path
- end
+ let(:pipeline) { create(:ci_pipeline) }
+
+ context "when there are runners" do
+ before do
+ runner = FactoryGirl.create(:ci_runner, contacted_at: Time.now)
+ FactoryGirl.create(:ci_build, pipeline: pipeline, runner_id: runner.id)
+ visit admin_runners_path
+ end
+
+ it 'has all necessary texts' do
+ expect(page).to have_text "To register a new Runner"
+ expect(page).to have_text "Runners with last contact more than a minute ago: 1"
+ end
+
+ describe 'search' do
+ before do
+ FactoryGirl.create :ci_runner, description: 'runner-foo'
+ FactoryGirl.create :ci_runner, description: 'runner-bar'
+ end
+
+ it 'shows correct runner when description matches' do
+ search_form = find('#runners-search')
+ search_form.fill_in 'search', with: 'runner-foo'
+ search_form.click_button 'Search'
+
+ expect(page).to have_content("runner-foo")
+ expect(page).not_to have_content("runner-bar")
+ end
+
+ it 'shows no runner when description does not match' do
+ search_form = find('#runners-search')
+ search_form.fill_in 'search', with: 'runner-baz'
+ search_form.click_button 'Search'
- it 'has all necessary texts' do
- expect(page).to have_text "To register a new Runner"
- expect(page).to have_text "Runners with last contact more than a minute ago: 1"
+ expect(page).to have_text 'No runners found'
+ end
+ end
end
- describe 'search' do
+ context "when there are no runners" do
before do
- FactoryGirl.create :ci_runner, description: 'runner-foo'
- FactoryGirl.create :ci_runner, description: 'runner-bar'
-
- search_form = find('#runners-search')
- search_form.fill_in 'search', with: 'runner-foo'
- search_form.click_button 'Search'
+ visit admin_runners_path
end
- it 'shows correct runner' do
- expect(page).to have_content("runner-foo")
- expect(page).not_to have_content("runner-bar")
+ it 'has all necessary texts including no runner message' do
+ expect(page).to have_text "To register a new Runner"
+ expect(page).to have_text "Runners with last contact more than a minute ago: 0"
+ expect(page).to have_text 'No runners found'
end
end
end
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 69d5500848e..bb1fb5b3feb 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -1,18 +1,24 @@
require 'spec_helper'
-describe 'Dashboard Merge Requests' do
+feature 'Dashboard Merge Requests' do
+ include FilterItemSelectHelper
+
let(:current_user) { create :user }
let(:project) { create(:empty_project) }
- let(:project_with_merge_requests_disabled) { create(:empty_project, :merge_requests_disabled) }
- before do
- [project, project_with_merge_requests_disabled].each { |project| project.team << [current_user, :master] }
+ let(:public_project) { create(:empty_project, :public, :repository) }
+ let(:forked_project) { Projects::ForkService.new(public_project, current_user).execute }
- gitlab_sign_in(current_user)
+ before do
+ project.add_master(current_user)
+ sign_in(current_user)
end
- describe 'new merge request dropdown' do
+ context 'new merge request dropdown' do
+ let(:project_with_disabled_merge_requests) { create(:empty_project, :merge_requests_disabled) }
+
before do
+ project_with_disabled_merge_requests.add_master(current_user)
visit merge_requests_dashboard_path
end
@@ -21,26 +27,87 @@ describe 'Dashboard Merge Requests' do
page.within('.select2-results') do
expect(page).to have_content(project.name_with_namespace)
- expect(page).not_to have_content(project_with_merge_requests_disabled.name_with_namespace)
+ expect(page).not_to have_content(project_with_disabled_merge_requests.name_with_namespace)
end
end
end
- it 'should show an empty state' do
- visit merge_requests_dashboard_path(assignee_id: current_user.id)
+ context 'no merge requests exist' do
+ it 'shows an empty state' do
+ visit merge_requests_dashboard_path(assignee_id: current_user.id)
- expect(page).to have_selector('.empty-state')
+ expect(page).to have_selector('.empty-state')
+ end
end
- context 'if there are merge requests' do
- before do
- create(:merge_request, assignee: current_user, source_project: project)
+ context 'merge requests exist' do
+ let!(:assigned_merge_request) do
+ create(:merge_request, assignee: current_user, target_project: project, source_project: project)
+ end
+
+ let!(:assigned_merge_request_from_fork) do
+ create(:merge_request,
+ source_branch: 'markdown', assignee: current_user,
+ target_project: public_project, source_project: forked_project
+ )
+ end
+ let!(:authored_merge_request) do
+ create(:merge_request,
+ source_branch: 'markdown', author: current_user,
+ target_project: project, source_project: project
+ )
+ end
+
+ let!(:authored_merge_request_from_fork) do
+ create(:merge_request,
+ source_branch: 'feature_conflict',
+ author: current_user,
+ target_project: public_project, source_project: forked_project
+ )
+ end
+
+ let!(:other_merge_request) do
+ create(:merge_request,
+ source_branch: 'fix',
+ target_project: project, source_project: project
+ )
+ end
+
+ before do
visit merge_requests_dashboard_path(assignee_id: current_user.id)
end
- it 'should not show an empty state' do
- expect(page).not_to have_selector('.empty-state')
+ it 'shows assigned merge requests' do
+ expect(page).to have_content(assigned_merge_request.title)
+ expect(page).to have_content(assigned_merge_request_from_fork.title)
+
+ expect(page).not_to have_content(authored_merge_request.title)
+ expect(page).not_to have_content(authored_merge_request_from_fork.title)
+ expect(page).not_to have_content(other_merge_request.title)
+ end
+
+ it 'shows authored merge requests', js: true do
+ filter_item_select('Any Assignee', '.js-assignee-search')
+ filter_item_select(current_user.to_reference, '.js-author-search')
+
+ expect(page).to have_content(authored_merge_request.title)
+ expect(page).to have_content(authored_merge_request_from_fork.title)
+
+ expect(page).not_to have_content(assigned_merge_request.title)
+ expect(page).not_to have_content(assigned_merge_request_from_fork.title)
+ expect(page).not_to have_content(other_merge_request.title)
+ end
+
+ it 'shows all merge requests', js: true do
+ filter_item_select('Any Assignee', '.js-assignee-search')
+ filter_item_select('Any Author', '.js-author-search')
+
+ expect(page).to have_content(authored_merge_request.title)
+ expect(page).to have_content(authored_merge_request_from_fork.title)
+ expect(page).to have_content(assigned_merge_request.title)
+ expect(page).to have_content(assigned_merge_request_from_fork.title)
+ expect(page).to have_content(other_merge_request.title)
end
end
end
diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb
index 295262980a6..b0e4036f27c 100644
--- a/spec/features/dashboard/milestone_filter_spec.rb
+++ b/spec/features/dashboard/milestone_filter_spec.rb
@@ -1,10 +1,12 @@
require 'spec_helper'
-describe 'Dashboard > milestone filter', :feature, :js do
+feature 'Dashboard > milestone filter', :feature, :js do
+ include FilterItemSelectHelper
+
let(:user) { create(:user) }
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
- let(:milestone) { create(:milestone, title: "v1.0", project: project) }
- let(:milestone2) { create(:milestone, title: "v2.0", project: project) }
+ let(:milestone) { create(:milestone, title: 'v1.0', project: project) }
+ let(:milestone2) { create(:milestone, title: 'v2.0', project: project) }
let!(:issue) { create :issue, author: user, project: project, milestone: milestone }
let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 }
@@ -22,17 +24,11 @@ describe 'Dashboard > milestone filter', :feature, :js do
end
context 'filtering by milestone' do
- milestone_select = '.js-milestone-select'
+ milestone_select_selector = '.js-milestone-select'
before do
- find(milestone_select).click
- wait_for_requests
-
- page.within('.dropdown-content') do
- click_link 'v1.0'
- end
-
- find(milestone_select).click
+ filter_item_select('v1.0', milestone_select_selector)
+ find(milestone_select_selector).click
wait_for_requests
end
@@ -49,7 +45,7 @@ describe 'Dashboard > milestone filter', :feature, :js do
expect(find('.milestone-filter')).not_to have_selector('.dropdown.open')
- find(milestone_select).click
+ find(milestone_select_selector).click
expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
expect(find('.dropdown-content a.is-active')).to have_content('v1.0')
diff --git a/spec/features/todos/target_state_spec.rb b/spec/features/dashboard/todos/target_state_spec.rb
index 99b70b3d3a1..030a86d1c01 100644
--- a/spec/features/todos/target_state_spec.rb
+++ b/spec/features/dashboard/todos/target_state_spec.rb
@@ -1,12 +1,12 @@
require 'rails_helper'
-feature 'Todo target states', feature: true do
+feature 'Dashboard > Todo target states' do
let(:user) { create(:user) }
let(:author) { create(:user) }
- let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+ let(:project) { create(:project, :public) }
before do
- gitlab_sign_in user
+ sign_in(user)
end
scenario 'on a closed issue todo has closed label' do
@@ -30,7 +30,7 @@ feature 'Todo target states', feature: true do
end
scenario 'on a merged merge request todo has merged label' do
- mr_merged = create(:merge_request, :simple, author: user, state: 'merged')
+ mr_merged = create(:merge_request, :simple, :merged, author: user)
create_todo mr_merged
visit dashboard_todos_path
@@ -40,7 +40,7 @@ feature 'Todo target states', feature: true do
end
scenario 'on a closed merge request todo has closed label' do
- mr_closed = create(:merge_request, :simple, author: user, state: 'closed')
+ mr_closed = create(:merge_request, :simple, :closed, author: user)
create_todo mr_closed
visit dashboard_todos_path
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb
index 032fb479076..0a363259fe7 100644
--- a/spec/features/todos/todos_filtering_spec.rb
+++ b/spec/features/dashboard/todos/todos_filtering_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Dashboard > User filters todos', feature: true, js: true do
+feature 'Dashboard > User filters todos', js: true do
let(:user_1) { create(:user, username: 'user_1', name: 'user_1') }
let(:user_2) { create(:user, username: 'user_2', name: 'user_2') }
@@ -17,7 +17,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
project_1.team << [user_1, :developer]
project_2.team << [user_1, :developer]
- gitlab_sign_in(user_1)
+ sign_in(user_1)
visit dashboard_todos_path
end
@@ -34,7 +34,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
expect(page).not_to have_content project_2.name_with_namespace
end
- context "Author filter" do
+ context 'Author filter' do
it 'filters by author' do
click_button 'Author'
@@ -49,18 +49,18 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
expect(find('.todos-list')).not_to have_content 'issue'
end
- it "shows only authors of existing todos" do
+ it 'shows only authors of existing todos' do
click_button 'Author'
within '.dropdown-menu-author' do
- # It should contain two users + "Any Author"
+ # It should contain two users + 'Any Author'
expect(page).to have_selector('.dropdown-menu-user-link', count: 3)
expect(page).to have_content(user_1.name)
expect(page).to have_content(user_2.name)
end
end
- it "shows only authors of existing done todos" do
+ it 'shows only authors of existing done todos' do
user_3 = create :user
user_4 = create :user
create(:todo, user: user_1, author: user_3, project: project_1, target: issue, action: 1, state: :done)
@@ -74,7 +74,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
click_button 'Author'
within '.dropdown-menu-author' do
- # It should contain two users + "Any Author"
+ # It should contain two users + 'Any Author'
expect(page).to have_selector('.dropdown-menu-user-link', count: 3)
expect(page).to have_content(user_3.name)
expect(page).to have_content(user_4.name)
diff --git a/spec/features/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb
index 498bbac6d14..5858f4aa101 100644
--- a/spec/features/todos/todos_sorting_spec.rb
+++ b/spec/features/dashboard/todos/todos_sorting_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Dashboard > User sorts todos", feature: true do
+feature 'Dashboard > User sorts todos' do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
@@ -18,7 +18,7 @@ describe "Dashboard > User sorts todos", feature: true do
let(:issue_3) { create(:issue, title: 'issue_3', project: project) }
let(:issue_4) { create(:issue, title: 'issue_4', project: project) }
- let!(:merge_request_1) { create(:merge_request, source_project: project, title: "merge_request_1") }
+ let!(:merge_request_1) { create(:merge_request, source_project: project, title: 'merge_request_1') }
before do
create(:todo, user: user, project: project, target: issue_4, created_at: 5.hours.ago)
@@ -32,41 +32,41 @@ describe "Dashboard > User sorts todos", feature: true do
issue_2.labels << label_3
issue_1.labels << label_2
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_todos_path
end
- it "sorts with oldest created todos first" do
- click_link "Last created"
+ it 'sorts with oldest created todos first' do
+ click_link 'Last created'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content("merge_request_1")
- expect(results_list.all('p')[1]).to have_content("issue_1")
- expect(results_list.all('p')[2]).to have_content("issue_3")
- expect(results_list.all('p')[3]).to have_content("issue_2")
- expect(results_list.all('p')[4]).to have_content("issue_4")
+ expect(results_list.all('p')[0]).to have_content('merge_request_1')
+ expect(results_list.all('p')[1]).to have_content('issue_1')
+ expect(results_list.all('p')[2]).to have_content('issue_3')
+ expect(results_list.all('p')[3]).to have_content('issue_2')
+ expect(results_list.all('p')[4]).to have_content('issue_4')
end
- it "sorts with newest created todos first" do
- click_link "Oldest created"
+ it 'sorts with newest created todos first' do
+ click_link 'Oldest created'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content("issue_4")
- expect(results_list.all('p')[1]).to have_content("issue_2")
- expect(results_list.all('p')[2]).to have_content("issue_3")
- expect(results_list.all('p')[3]).to have_content("issue_1")
- expect(results_list.all('p')[4]).to have_content("merge_request_1")
+ expect(results_list.all('p')[0]).to have_content('issue_4')
+ expect(results_list.all('p')[1]).to have_content('issue_2')
+ expect(results_list.all('p')[2]).to have_content('issue_3')
+ expect(results_list.all('p')[3]).to have_content('issue_1')
+ expect(results_list.all('p')[4]).to have_content('merge_request_1')
end
- it "sorts by label priority" do
- click_link "Label priority"
+ it 'sorts by label priority' do
+ click_link 'Label priority'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content("issue_3")
- expect(results_list.all('p')[1]).to have_content("merge_request_1")
- expect(results_list.all('p')[2]).to have_content("issue_1")
- expect(results_list.all('p')[3]).to have_content("issue_2")
- expect(results_list.all('p')[4]).to have_content("issue_4")
+ expect(results_list.all('p')[0]).to have_content('issue_3')
+ expect(results_list.all('p')[1]).to have_content('merge_request_1')
+ expect(results_list.all('p')[2]).to have_content('issue_1')
+ expect(results_list.all('p')[3]).to have_content('issue_2')
+ expect(results_list.all('p')[4]).to have_content('issue_4')
end
end
@@ -88,12 +88,12 @@ describe "Dashboard > User sorts todos", feature: true do
end
it "doesn't mix issues and merge requests label priorities" do
- click_link "Label priority"
+ click_link 'Label priority'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content("issue_1")
- expect(results_list.all('p')[1]).to have_content("issue_2")
- expect(results_list.all('p')[2]).to have_content("merge_request_1")
+ expect(results_list.all('p')[0]).to have_content('issue_1')
+ expect(results_list.all('p')[1]).to have_content('issue_2')
+ expect(results_list.all('p')[2]).to have_content('merge_request_1')
end
end
end
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
new file mode 100644
index 00000000000..24da5db305f
--- /dev/null
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -0,0 +1,355 @@
+require 'spec_helper'
+
+feature 'Dashboard Todos' do
+ let(:user) { create(:user) }
+ let(:author) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, due_date: Date.today) }
+
+ context 'User does not have todos' do
+ before do
+ sign_in(user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows "All done" message' do
+ expect(page).to have_content 'Todos let you see what you should do next.'
+ end
+ end
+
+ context 'User has a todo', js: true do
+ before do
+ create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
+ sign_in(user)
+
+ visit dashboard_todos_path
+ end
+
+ it 'has todo present' do
+ expect(page).to have_selector('.todos-list .todo', count: 1)
+ end
+
+ it 'shows due date as today' do
+ within first('.todo') do
+ expect(page).to have_content 'Due today'
+ end
+ end
+
+ shared_examples 'deleting the todo' do
+ before do
+ within first('.todo') do
+ click_link 'Done'
+ end
+ end
+
+ it 'is marked as done-reversible in the list' do
+ expect(page).to have_selector('.todos-list .todo.todo-pending.done-reversible')
+ end
+
+ it 'shows Undo button' do
+ expect(page).to have_selector('.js-undo-todo', visible: true)
+ expect(page).to have_selector('.js-done-todo', visible: false)
+ end
+
+ it 'updates todo count' do
+ expect(page).to have_content 'To do 0'
+ expect(page).to have_content 'Done 1'
+ end
+
+ it 'has not "All done" message' do
+ expect(page).not_to have_selector('.todos-all-done')
+ end
+ end
+
+ shared_examples 'deleting and restoring the todo' do
+ before do
+ within first('.todo') do
+ click_link 'Done'
+ wait_for_requests
+ click_link 'Undo'
+ end
+ end
+
+ it 'is marked back as pending in the list' do
+ expect(page).not_to have_selector('.todos-list .todo.todo-pending.done-reversible')
+ expect(page).to have_selector('.todos-list .todo.todo-pending')
+ end
+
+ it 'shows Done button' do
+ expect(page).to have_selector('.js-undo-todo', visible: false)
+ expect(page).to have_selector('.js-done-todo', visible: true)
+ end
+
+ it 'updates todo count' do
+ expect(page).to have_content 'To do 1'
+ expect(page).to have_content 'Done 0'
+ end
+ end
+
+ it_behaves_like 'deleting the todo'
+ it_behaves_like 'deleting and restoring the todo'
+
+ context 'todo is stale on the page' do
+ before do
+ todos = TodosFinder.new(user, state: :pending).execute
+ TodoService.new.mark_todos_as_done(todos, user)
+ end
+
+ it_behaves_like 'deleting the todo'
+ it_behaves_like 'deleting and restoring the todo'
+ end
+ end
+
+ context 'User created todos for themself' do
+ before do
+ sign_in(user)
+ end
+
+ context 'issue assigned todo' do
+ before do
+ create(:todo, :assigned, user: user, project: project, target: issue, author: user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows issue assigned to yourself message' do
+ page.within('.js-todos-all') do
+ expect(page).to have_content("You assigned issue #{issue.to_reference(full: true)} to yourself")
+ end
+ end
+ end
+
+ context 'marked todo' do
+ before do
+ create(:todo, :marked, user: user, project: project, target: issue, author: user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows you added a todo message' do
+ page.within('.js-todos-all') do
+ expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}")
+ expect(page).not_to have_content('to yourself')
+ end
+ end
+ end
+
+ context 'mentioned todo' do
+ before do
+ create(:todo, :mentioned, user: user, project: project, target: issue, author: user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows you mentioned yourself message' do
+ page.within('.js-todos-all') do
+ expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}")
+ expect(page).not_to have_content('to yourself')
+ end
+ end
+ end
+
+ context 'directly_addressed todo' do
+ before do
+ create(:todo, :directly_addressed, user: user, project: project, target: issue, author: user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows you directly addressed yourself message' do
+ page.within('.js-todos-all') do
+ expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}")
+ expect(page).not_to have_content('to yourself')
+ end
+ end
+ end
+
+ context 'approval todo' do
+ let(:merge_request) { create(:merge_request) }
+
+ before do
+ create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows you set yourself as an approver message' do
+ page.within('.js-todos-all') do
+ expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}")
+ expect(page).not_to have_content('to yourself')
+ end
+ end
+ end
+ end
+
+ context 'User has done todos', js: true do
+ before do
+ create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author)
+ sign_in(user)
+ visit dashboard_todos_path(state: :done)
+ end
+
+ it 'has the done todo present' do
+ expect(page).to have_selector('.todos-list .todo.todo-done', count: 1)
+ end
+
+ describe 'restoring the todo' do
+ before do
+ within first('.todo') do
+ click_link 'Add todo'
+ end
+ end
+
+ it 'is removed from the list' do
+ expect(page).not_to have_selector('.todos-list .todo.todo-done')
+ end
+
+ it 'updates todo count' do
+ expect(page).to have_content 'To do 1'
+ expect(page).to have_content 'Done 0'
+ end
+ end
+ end
+
+ context 'User has Todos with labels spanning multiple projects' do
+ before do
+ label1 = create(:label, project: project)
+ note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project)
+ create(:todo, :mentioned, project: project, target: issue, user: user, note_id: note1.id)
+
+ project2 = create(:project, :public)
+ label2 = create(:label, project: project2)
+ issue2 = create(:issue, project: project2)
+ note2 = create(:note_on_issue, note: "Test #{label2.to_reference(format: :name)}", noteable_id: issue2.id, noteable_type: 'Issue', project: project2)
+ create(:todo, :mentioned, project: project2, target: issue2, user: user, note_id: note2.id)
+
+ gitlab_sign_in(user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows page with two Todos' do
+ expect(page).to have_selector('.todos-list .todo', count: 2)
+ end
+ end
+
+ context 'User has multiple pages of Todos' do
+ before do
+ allow(Todo).to receive(:default_per_page).and_return(1)
+
+ # Create just enough records to cause us to paginate
+ create_list(:todo, 2, :mentioned, user: user, project: project, target: issue, author: author)
+
+ sign_in(user)
+ end
+
+ it 'is paginated' do
+ visit dashboard_todos_path
+
+ expect(page).to have_selector('.gl-pagination')
+ end
+
+ it 'is has the right number of pages' do
+ visit dashboard_todos_path
+
+ expect(page).to have_selector('.gl-pagination .page', count: 2)
+ end
+
+ describe 'mark all as done', js: true do
+ before do
+ visit dashboard_todos_path
+ find('.js-todos-mark-all').trigger('click')
+ end
+
+ it 'shows "All done" message!' do
+ expect(page).to have_content 'To do 0'
+ expect(page).to have_content "You're all done!"
+ expect(page).not_to have_selector('.gl-pagination')
+ end
+
+ it 'shows "Undo mark all as done" button' do
+ expect(page).to have_selector('.js-todos-mark-all', visible: false)
+ expect(page).to have_selector('.js-todos-undo-all', visible: true)
+ end
+ end
+
+ describe 'undo mark all as done', js: true do
+ before do
+ visit dashboard_todos_path
+ end
+
+ it 'shows the restored todo list' do
+ mark_all_and_undo
+
+ expect(page).to have_selector('.todos-list .todo', count: 1)
+ expect(page).to have_selector('.gl-pagination')
+ expect(page).not_to have_content "You're all done!"
+ end
+
+ it 'updates todo count' do
+ mark_all_and_undo
+
+ expect(page).to have_content 'To do 2'
+ expect(page).to have_content 'Done 0'
+ end
+
+ it 'shows "Mark all as done" button' do
+ mark_all_and_undo
+
+ expect(page).to have_selector('.js-todos-mark-all', visible: true)
+ expect(page).to have_selector('.js-todos-undo-all', visible: false)
+ end
+
+ context 'User has deleted a todo' do
+ before do
+ within first('.todo') do
+ click_link 'Done'
+ end
+ end
+
+ it 'shows the restored todo list with the deleted todo' do
+ mark_all_and_undo
+
+ expect(page).to have_selector('.todos-list .todo.todo-pending', count: 1)
+ end
+ end
+
+ def mark_all_and_undo
+ find('.js-todos-mark-all').trigger('click')
+ wait_for_requests
+ find('.js-todos-undo-all').trigger('click')
+ wait_for_requests
+ end
+ end
+ end
+
+ context 'User has a Todo in a project pending deletion' do
+ before do
+ deleted_project = create(:project, :public, pending_delete: true)
+ create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author)
+ create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author, state: :done)
+ sign_in(user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows "All done" message' do
+ within('.todos-count') { expect(page).to have_content '0' }
+ expect(page).to have_content 'To do 0'
+ expect(page).to have_content 'Done 0'
+ expect(page).to have_selector('.todos-all-done', count: 1)
+ end
+ end
+
+ context 'User has a Build Failed todo' do
+ let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) }
+
+ before do
+ sign_in(user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows the todo' do
+ expect(page).to have_content 'The build failed for merge request'
+ end
+
+ it 'links to the pipelines for the merge request' do
+ href = pipelines_namespace_project_merge_request_path(project.namespace, project, todo.target)
+
+ expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href
+ end
+ end
+end
diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb
new file mode 100644
index 00000000000..8b891c52d08
--- /dev/null
+++ b/spec/features/groups/labels/subscription_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+feature 'Labels subscription', feature: true do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let!(:feature) { create(:group_label, group: group, title: 'feature') }
+
+ context 'when signed in' do
+ before do
+ group.add_developer(user)
+ gitlab_sign_in user
+ end
+
+ scenario 'users can subscribe/unsubscribe to group labels', js: true do
+ visit group_labels_path(group)
+
+ expect(page).to have_content('feature')
+
+ within "#group_label_#{feature.id}" do
+ expect(page).not_to have_button 'Unsubscribe'
+
+ click_button 'Subscribe'
+
+ expect(page).not_to have_button 'Subscribe'
+ expect(page).to have_button 'Unsubscribe'
+
+ click_button 'Unsubscribe'
+
+ expect(page).to have_button 'Subscribe'
+ expect(page).not_to have_button 'Unsubscribe'
+ end
+ end
+ end
+
+ context 'when not signed in' do
+ it 'users can not subscribe/unsubscribe to labels' do
+ visit group_labels_path(group)
+
+ expect(page).to have_content 'feature'
+ expect(page).not_to have_button('Subscribe')
+ end
+ end
+
+ def click_link_on_dropdown(text)
+ find('.dropdown-group-label').click
+
+ page.within('.dropdown-group-label') do
+ find('a.js-subscribe-button', text: text).click
+ end
+ end
+end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 53b8ba5b0f7..a8055b21cee 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -143,29 +143,8 @@ feature 'Login', feature: true do
end
context 'logging in via OAuth' do
- def saml_config
- OpenStruct.new(name: 'saml', label: 'saml', args: {
- assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback',
- idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52',
- idp_sso_target_url: 'https://idp.example.com/sso/saml',
- issuer: 'https://localhost:3443/',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
- })
- end
-
- def stub_omniauth_config(messages)
- Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
- Rails.application.routes.disable_clear_and_finalize = true
- Rails.application.routes.draw do
- post '/users/auth/saml' => 'omniauth_callbacks#saml'
- end
- allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config)
- allow(Gitlab.config.omniauth).to receive_messages(messages)
- expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
- end
-
it 'shows 2FA prompt after OAuth login' do
- stub_omniauth_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config])
+ stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')
gitlab_sign_in_via('saml', user, 'my-uid')
diff --git a/spec/features/projects/no_password_spec.rb b/spec/features/projects/no_password_spec.rb
new file mode 100644
index 00000000000..30a16e38e3c
--- /dev/null
+++ b/spec/features/projects/no_password_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+feature 'No Password Alert' do
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ context 'with internal auth enabled' do
+ before do
+ sign_in(user)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ context 'when user has a password' do
+ let(:user) { create(:user) }
+
+ it 'shows no alert' do
+ expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account"
+ end
+ end
+
+ context 'when user has password automatically set' do
+ let(:user) { create(:user, password_automatically_set: true) }
+
+ it 'shows a password alert' do
+ expect(page).to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account"
+ end
+ end
+ end
+
+ context 'with internal auth disabled' do
+ let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') }
+
+ before do
+ stub_application_setting(signin_enabled?: false)
+ stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
+ end
+
+ context 'when user has no personal access tokens' do
+ it 'has a personal access token alert' do
+ gitlab_sign_in_via('saml', user, 'my-uid')
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
+ end
+ end
+
+ context 'when user has a personal access token' do
+ it 'shows no alert' do
+ create(:personal_access_token, user: user)
+ gitlab_sign_in_via('saml', user, 'my-uid')
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
+ end
+ end
+ end
+
+ context 'when user is ldap user' do
+ let(:user) { create(:omniauth_user, password_automatically_set: true) }
+
+ before do
+ sign_in(user)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ it 'shows no alert' do
+ expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you"
+ end
+ end
+end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
deleted file mode 100644
index 41b32bdedc3..00000000000
--- a/spec/features/todos/todos_spec.rb
+++ /dev/null
@@ -1,355 +0,0 @@
-require 'spec_helper'
-
-describe 'Dashboard Todos', feature: true do
- let(:user) { create(:user) }
- let(:author) { create(:user) }
- let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:issue) { create(:issue, due_date: Date.today) }
-
- describe 'GET /dashboard/todos' do
- context 'User does not have todos' do
- before do
- gitlab_sign_in(user)
- visit dashboard_todos_path
- end
- it 'shows "All done" message' do
- expect(page).to have_content "Todos let you see what you should do next."
- end
- end
-
- context 'User has a todo', js: true do
- before do
- create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
- gitlab_sign_in(user)
- visit dashboard_todos_path
- end
-
- it 'has todo present' do
- expect(page).to have_selector('.todos-list .todo', count: 1)
- end
-
- it 'shows due date as today' do
- within first('.todo') do
- expect(page).to have_content 'Due today'
- end
- end
-
- shared_examples 'deleting the todo' do
- before do
- within first('.todo') do
- click_link 'Done'
- end
- end
-
- it 'is marked as done-reversible in the list' do
- expect(page).to have_selector('.todos-list .todo.todo-pending.done-reversible')
- end
-
- it 'shows Undo button' do
- expect(page).to have_selector('.js-undo-todo', visible: true)
- expect(page).to have_selector('.js-done-todo', visible: false)
- end
-
- it 'updates todo count' do
- expect(page).to have_content 'To do 0'
- expect(page).to have_content 'Done 1'
- end
-
- it 'has not "All done" message' do
- expect(page).not_to have_selector('.todos-all-done')
- end
- end
-
- shared_examples 'deleting and restoring the todo' do
- before do
- within first('.todo') do
- click_link 'Done'
- wait_for_requests
- click_link 'Undo'
- end
- end
-
- it 'is marked back as pending in the list' do
- expect(page).not_to have_selector('.todos-list .todo.todo-pending.done-reversible')
- expect(page).to have_selector('.todos-list .todo.todo-pending')
- end
-
- it 'shows Done button' do
- expect(page).to have_selector('.js-undo-todo', visible: false)
- expect(page).to have_selector('.js-done-todo', visible: true)
- end
-
- it 'updates todo count' do
- expect(page).to have_content 'To do 1'
- expect(page).to have_content 'Done 0'
- end
- end
-
- it_behaves_like 'deleting the todo'
- it_behaves_like 'deleting and restoring the todo'
-
- context 'todo is stale on the page' do
- before do
- todos = TodosFinder.new(user, state: :pending).execute
- TodoService.new.mark_todos_as_done(todos, user)
- end
-
- it_behaves_like 'deleting the todo'
- it_behaves_like 'deleting and restoring the todo'
- end
- end
-
- context 'User created todos for themself' do
- before do
- gitlab_sign_in(user)
- end
-
- context 'issue assigned todo' do
- before do
- create(:todo, :assigned, user: user, project: project, target: issue, author: user)
- visit dashboard_todos_path
- end
-
- it 'shows issue assigned to yourself message' do
- page.within('.js-todos-all') do
- expect(page).to have_content("You assigned issue #{issue.to_reference(full: true)} to yourself")
- end
- end
- end
-
- context 'marked todo' do
- before do
- create(:todo, :marked, user: user, project: project, target: issue, author: user)
- visit dashboard_todos_path
- end
-
- it 'shows you added a todo message' do
- page.within('.js-todos-all') do
- expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}")
- expect(page).not_to have_content('to yourself')
- end
- end
- end
-
- context 'mentioned todo' do
- before do
- create(:todo, :mentioned, user: user, project: project, target: issue, author: user)
- visit dashboard_todos_path
- end
-
- it 'shows you mentioned yourself message' do
- page.within('.js-todos-all') do
- expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}")
- expect(page).not_to have_content('to yourself')
- end
- end
- end
-
- context 'directly_addressed todo' do
- before do
- create(:todo, :directly_addressed, user: user, project: project, target: issue, author: user)
- visit dashboard_todos_path
- end
-
- it 'shows you directly addressed yourself message' do
- page.within('.js-todos-all') do
- expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}")
- expect(page).not_to have_content('to yourself')
- end
- end
- end
-
- context 'approval todo' do
- let(:merge_request) { create(:merge_request) }
-
- before do
- create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user)
- visit dashboard_todos_path
- end
-
- it 'shows you set yourself as an approver message' do
- page.within('.js-todos-all') do
- expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}")
- expect(page).not_to have_content('to yourself')
- end
- end
- end
- end
-
- context 'User has done todos', js: true do
- before do
- create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author)
- gitlab_sign_in(user)
- visit dashboard_todos_path(state: :done)
- end
-
- it 'has the done todo present' do
- expect(page).to have_selector('.todos-list .todo.todo-done', count: 1)
- end
-
- describe 'restoring the todo' do
- before do
- within first('.todo') do
- click_link 'Add todo'
- end
- end
-
- it 'is removed from the list' do
- expect(page).not_to have_selector('.todos-list .todo.todo-done')
- end
-
- it 'updates todo count' do
- expect(page).to have_content 'To do 1'
- expect(page).to have_content 'Done 0'
- end
- end
- end
-
- context 'User has Todos with labels spanning multiple projects' do
- before do
- label1 = create(:label, project: project)
- note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project)
- create(:todo, :mentioned, project: project, target: issue, user: user, note_id: note1.id)
-
- project2 = create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
- label2 = create(:label, project: project2)
- issue2 = create(:issue, project: project2)
- note2 = create(:note_on_issue, note: "Test #{label2.to_reference(format: :name)}", noteable_id: issue2.id, noteable_type: 'Issue', project: project2)
- create(:todo, :mentioned, project: project2, target: issue2, user: user, note_id: note2.id)
-
- gitlab_sign_in(user)
- visit dashboard_todos_path
- end
-
- it 'shows page with two Todos' do
- expect(page).to have_selector('.todos-list .todo', count: 2)
- end
- end
-
- context 'User has multiple pages of Todos' do
- before do
- allow(Todo).to receive(:default_per_page).and_return(1)
-
- # Create just enough records to cause us to paginate
- create_list(:todo, 2, :mentioned, user: user, project: project, target: issue, author: author)
-
- gitlab_sign_in(user)
- end
-
- it 'is paginated' do
- visit dashboard_todos_path
-
- expect(page).to have_selector('.gl-pagination')
- end
-
- it 'is has the right number of pages' do
- visit dashboard_todos_path
-
- expect(page).to have_selector('.gl-pagination .page', count: 2)
- end
-
- describe 'mark all as done', js: true do
- before do
- visit dashboard_todos_path
- find('.js-todos-mark-all').trigger('click')
- end
-
- it 'shows "All done" message!' do
- expect(page).to have_content 'To do 0'
- expect(page).to have_content "You're all done!"
- expect(page).not_to have_selector('.gl-pagination')
- end
-
- it 'shows "Undo mark all as done" button' do
- expect(page).to have_selector('.js-todos-mark-all', visible: false)
- expect(page).to have_selector('.js-todos-undo-all', visible: true)
- end
- end
-
- describe 'undo mark all as done', js: true do
- before do
- visit dashboard_todos_path
- end
-
- it 'shows the restored todo list' do
- mark_all_and_undo
-
- expect(page).to have_selector('.todos-list .todo', count: 1)
- expect(page).to have_selector('.gl-pagination')
- expect(page).not_to have_content "You're all done!"
- end
-
- it 'updates todo count' do
- mark_all_and_undo
-
- expect(page).to have_content 'To do 2'
- expect(page).to have_content 'Done 0'
- end
-
- it 'shows "Mark all as done" button' do
- mark_all_and_undo
-
- expect(page).to have_selector('.js-todos-mark-all', visible: true)
- expect(page).to have_selector('.js-todos-undo-all', visible: false)
- end
-
- context 'User has deleted a todo' do
- before do
- within first('.todo') do
- click_link 'Done'
- end
- end
-
- it 'shows the restored todo list with the deleted todo' do
- mark_all_and_undo
-
- expect(page).to have_selector('.todos-list .todo.todo-pending', count: 1)
- end
- end
-
- def mark_all_and_undo
- find('.js-todos-mark-all').trigger('click')
- wait_for_requests
- find('.js-todos-undo-all').trigger('click')
- wait_for_requests
- end
- end
- end
-
- context 'User has a Todo in a project pending deletion' do
- before do
- deleted_project = create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC, pending_delete: true)
- create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author)
- create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author, state: :done)
- gitlab_sign_in(user)
- visit dashboard_todos_path
- end
-
- it 'shows "All done" message' do
- within('.todos-count') { expect(page).to have_content '0' }
- expect(page).to have_content 'To do 0'
- expect(page).to have_content 'Done 0'
- expect(page).to have_selector('.todos-all-done', count: 1)
- end
- end
-
- context 'User has a Build Failed todo' do
- let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) }
-
- before do
- gitlab_sign_in user
- visit dashboard_todos_path
- end
-
- it 'shows the todo' do
- expect(page).to have_content 'The build failed for merge request'
- end
-
- it 'links to the pipelines for the merge request' do
- href = pipelines_namespace_project_merge_request_path(project.namespace, project, todo.target)
-
- expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href
- end
- end
- end
-end
diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb
new file mode 100644
index 00000000000..661327d4432
--- /dev/null
+++ b/spec/helpers/button_helper_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe ButtonHelper do
+ describe 'http_clone_button' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:has_tooltip_class) { 'has-tooltip' }
+
+ def element
+ element = helper.http_clone_button(project)
+
+ Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
+ end
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ context 'with internal auth enabled' do
+ context 'when user has a password' do
+ it 'shows no tooltip' do
+ expect(element.attr('class')).not_to include(has_tooltip_class)
+ end
+ end
+
+ context 'when user has password automatically set' do
+ let(:user) { create(:user, password_automatically_set: true) }
+
+ it 'shows a password tooltip' do
+ expect(element.attr('class')).to include(has_tooltip_class)
+ expect(element.attr('data-title')).to eq('Set a password on your account to pull or push via HTTP.')
+ end
+ end
+ end
+
+ context 'with internal auth disabled' do
+ before do
+ stub_application_setting(signin_enabled?: false)
+ end
+
+ context 'when user has no personal access tokens' do
+ it 'has a personal access token tooltip ' do
+ expect(element.attr('class')).to include(has_tooltip_class)
+ expect(element.attr('data-title')).to eq('Create a personal access token on your account to pull or push via HTTP.')
+ end
+ end
+
+ context 'when user has a personal access token' do
+ it 'shows no tooltip' do
+ create(:personal_access_token, user: user)
+
+ expect(element.attr('class')).not_to include(has_tooltip_class)
+ end
+ end
+ end
+
+ context 'when user is ldap user' do
+ let(:user) { create(:omniauth_user, password_automatically_set: true) }
+
+ it 'shows no tooltip' do
+ expect(element.attr('class')).not_to include(has_tooltip_class)
+ end
+ end
+ end
+end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 84a60ce13fc..8da22dc78fa 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -84,7 +84,7 @@ describe GroupsHelper do
end
end
- describe 'group_title' do
+ describe 'group_title', :nested_groups do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 9a4086725d2..487d9800707 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -115,6 +115,82 @@ describe ProjectsHelper do
end
end
+ describe '#show_no_ssh_key_message?' do
+ let(:user) { create(:user) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ context 'user has no keys' do
+ it 'returns true' do
+ expect(helper.show_no_ssh_key_message?).to be_truthy
+ end
+ end
+
+ context 'user has an ssh key' do
+ it 'returns false' do
+ create(:personal_key, user: user)
+
+ expect(helper.show_no_ssh_key_message?).to be_falsey
+ end
+ end
+ end
+
+ describe '#show_no_password_message?' do
+ let(:user) { create(:user) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ context 'user has password set' do
+ it 'returns false' do
+ expect(helper.show_no_password_message?).to be_falsey
+ end
+ end
+
+ context 'user requires a password' do
+ let(:user) { create(:user, password_automatically_set: true) }
+
+ it 'returns true' do
+ expect(helper.show_no_password_message?).to be_truthy
+ end
+ end
+
+ context 'user requires a personal access token' do
+ it 'returns true' do
+ stub_application_setting(signin_enabled?: false)
+
+ expect(helper.show_no_password_message?).to be_truthy
+ end
+ end
+ end
+
+ describe '#link_to_set_password' do
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ context 'user requires a password' do
+ let(:user) { create(:user, password_automatically_set: true) }
+
+ it 'returns link to set a password' do
+ expect(helper.link_to_set_password).to match %r{<a href="#{edit_profile_password_path}">set a password</a>}
+ end
+ end
+
+ context 'user requires a personal access token' do
+ let(:user) { create(:user) }
+
+ it 'returns link to create a personal access token' do
+ stub_application_setting(signin_enabled?: false)
+
+ expect(helper.link_to_set_password).to match %r{<a href="#{profile_personal_access_tokens_path}">create a personal access token</a>}
+ end
+ end
+ end
+
describe 'link_to_member' do
let(:group) { create(:group) }
let(:project) { create(:empty_project, group: group) }
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index a4f32a1faed..1b96b2e3d51 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -83,4 +83,47 @@ describe('Pipelines stage component', () => {
}, 0);
});
});
+
+ describe('update endpoint correctly', () => {
+ const updatedInterceptor = (request, next) => {
+ if (request.url === 'bar') {
+ next(request.respondWith(JSON.stringify({ html: 'this is the updated content' }), {
+ status: 200,
+ }));
+ }
+ next();
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(updatedInterceptor);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, updatedInterceptor,
+ );
+ });
+
+ it('should update the stage to request the new endpoint provided', (done) => {
+ component.stage = {
+ status: {
+ group: 'running',
+ icon: 'running',
+ title: 'running',
+ },
+ dropdown_path: 'bar',
+ };
+
+ Vue.nextTick(() => {
+ component.$el.querySelector('button').click();
+
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(),
+ ).toEqual('this is the updated content');
+ done();
+ });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/sidebar/assignee_title_spec.js b/spec/javascripts/sidebar/assignee_title_spec.js
index 5b5b1bf4140..ac93f918ce4 100644
--- a/spec/javascripts/sidebar/assignee_title_spec.js
+++ b/spec/javascripts/sidebar/assignee_title_spec.js
@@ -33,6 +33,31 @@ describe('AssigneeTitle component', () => {
});
});
+ describe('gutter toggle', () => {
+ it('does not show toggle by default', () => {
+ component = new AssigneeTitleComponent({
+ propsData: {
+ numberOfAssignees: 2,
+ editable: false,
+ },
+ }).$mount();
+
+ expect(component.$el.querySelector('.gutter-toggle')).toBeNull();
+ });
+
+ it('shows toggle when showToggle is true', () => {
+ component = new AssigneeTitleComponent({
+ propsData: {
+ numberOfAssignees: 2,
+ editable: false,
+ showToggle: true,
+ },
+ }).$mount();
+
+ expect(component.$el.querySelector('.gutter-toggle')).toEqual(jasmine.any(Object));
+ });
+ });
+
it('does not render spinner by default', () => {
component = new AssigneeTitleComponent({
propsData: {
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 647b59520f8..4b6f171c8d6 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -76,6 +76,28 @@ describe('MRWidgetPipeline', () => {
el = vm.$el;
});
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('without a pipeline', () => {
+ beforeEach(() => {
+ vm.mr = { pipeline: null };
+ });
+
+ it('should render message with spinner', (done) => {
+ Vue.nextTick()
+ .then(() => {
+ expect(el.querySelector('.pipeline-id')).toBe(null);
+ expect(el.innerText.trim()).toBe('Waiting for pipeline...');
+ expect(el.querySelectorAll('i.fa.fa-spinner.fa-spin').length).toBe(1);
+ done();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
it('should render template elements correctly', () => {
expect(el.classList.contains('mr-widget-heading')).toBeTruthy();
expect(el.querySelectorAll('.ci-status-icon.ci-status-icon-success').length).toEqual(1);
@@ -93,39 +115,47 @@ describe('MRWidgetPipeline', () => {
it('should list single stage', (done) => {
pipeline.details.stages.splice(0, 1);
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.stage-container button').length).toEqual(1);
- expect(el.innerText).toContain('with stage');
- done();
- });
+ Vue.nextTick()
+ .then(() => {
+ expect(el.querySelectorAll('.stage-container button').length).toEqual(1);
+ expect(el.innerText).toContain('with stage');
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should not have stages when there is no stage', (done) => {
vm.mr.pipeline.details.stages = [];
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.stage-container button').length).toEqual(0);
- done();
- });
+ Vue.nextTick()
+ .then(() => {
+ expect(el.querySelectorAll('.stage-container button').length).toEqual(0);
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should not have coverage text when pipeline has no coverage info', (done) => {
vm.mr.pipeline.coverage = null;
- Vue.nextTick(() => {
- expect(el.querySelector('.js-mr-coverage')).toEqual(null);
- done();
- });
+ Vue.nextTick()
+ .then(() => {
+ expect(el.querySelector('.js-mr-coverage')).toEqual(null);
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should show CI error when there is a CI error', (done) => {
vm.mr.ciStatus = null;
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
- expect(el.innerText).toContain('Could not connect to the CI server');
- done();
- });
+ Vue.nextTick()
+ .then(() => {
+ expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
+ expect(el.innerText).toContain('Could not connect to the CI server');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index a366d68a146..81bbd70ffb8 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -19,6 +19,19 @@ describe Gitlab::ExclusiveLease, type: :redis do
end
end
+ describe '#renew' do
+ it 'returns true when we have the existing lease' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ expect(lease.try_obtain).to be_present
+ expect(lease.renew).to be_truthy
+ end
+
+ it 'returns false when we dont have a lease' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ expect(lease.renew).to be_falsey
+ end
+ end
+
describe '#exists?' do
it 'returns true for an existing lease' do
lease = described_class.new(unique_key, timeout: 3600)
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index 42f3fc59f04..70796781532 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -44,6 +44,8 @@ describe 'forked project import', services: true do
end
it 'can access the MR' do
- expect(project.merge_requests.first.ensure_ref_fetched.first).to include('refs/merge-requests/1/head')
+ project.merge_requests.first.ensure_ref_fetched
+
+ expect(project.repository.ref_exists?('refs/merge-requests/1/head')).to be_truthy
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 1240c9745e2..bb5273074a2 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1574,4 +1574,40 @@ describe MergeRequest, models: true do
end
end
end
+
+ describe '#fetch_ref' do
+ it 'sets "ref_fetched" flag to true' do
+ subject.update!(ref_fetched: nil)
+
+ subject.fetch_ref
+
+ expect(subject.reload.ref_fetched).to be_truthy
+ end
+ end
+
+ describe '#ref_fetched?' do
+ it 'does not perform git operation when value is cached' do
+ subject.ref_fetched = true
+
+ expect_any_instance_of(Repository).not_to receive(:ref_exists?)
+ expect(subject.ref_fetched?).to be_truthy
+ end
+
+ it 'caches the value when ref exists but value is not cached' do
+ subject.update!(ref_fetched: nil)
+ allow_any_instance_of(Repository).to receive(:ref_exists?)
+ .and_return(true)
+
+ expect(subject.ref_fetched?).to be_truthy
+ expect(subject.reload.ref_fetched).to be_truthy
+ end
+
+ it 'returns false when ref does not exist' do
+ subject.update!(ref_fetched: nil)
+ allow_any_instance_of(Repository).to receive(:ref_exists?)
+ .and_return(false)
+
+ expect(subject.ref_fetched?).to be_falsey
+ end
+ end
end
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index c914b32f7a4..37f23b1243c 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -71,7 +71,7 @@ describe PrometheusService, models: true, caching: true do
end
describe '#deployment_metrics' do
- let(:deployment) { build_stubbed(:deployment)}
+ let(:deployment) { build_stubbed(:deployment) }
let(:deployment_query) { Gitlab::Prometheus::Queries::DeploymentQuery }
around do |example|
@@ -80,13 +80,16 @@ describe PrometheusService, models: true, caching: true do
context 'with valid data' do
subject { service.deployment_metrics(deployment) }
+ let(:fake_deployment_time) { 10 }
before do
stub_reactive_cache(service, prometheus_data, deployment_query, deployment.id)
end
it 'returns reactive data' do
- is_expected.to eq(prometheus_metrics_data.merge(deployment_time: deployment.created_at.to_i))
+ expect(deployment).to receive(:created_at).and_return(fake_deployment_time)
+
+ expect(subject).to eq(prometheus_metrics_data.merge(deployment_time: fake_deployment_time))
end
end
end
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 83673864fe7..e0975024b80 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -82,6 +82,17 @@ describe API::Variables do
expect(json_response['protected']).to be_truthy
end
+ it 'creates variable with optional attributes' do
+ expect do
+ post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
+ end.to change{project.variables.count}.by(1)
+
+ expect(response).to have_http_status(201)
+ expect(json_response['key']).to eq('TEST_VARIABLE_2')
+ expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['protected']).to be_falsey
+ end
+
it 'does not allow to duplicate variable key' do
expect do
post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2'
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 0d6dd28e332..697dc18feb0 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -15,8 +15,9 @@ describe Projects::DestroyService, services: true do
shared_examples 'deleting the project' do
it 'deletes the project' do
expect(Project.unscoped.all).not_to include(project)
- expect(Dir.exist?(path)).to be_falsey
- expect(Dir.exist?(remove_path)).to be_falsey
+
+ expect(project.gitlab_shell.exists?(project.repository_storage_path, path + '.git')).to be_falsey
+ expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path + '.git')).to be_falsey
end
end
diff --git a/spec/support/filter_item_select_helper.rb b/spec/support/filter_item_select_helper.rb
new file mode 100644
index 00000000000..519e84d359e
--- /dev/null
+++ b/spec/support/filter_item_select_helper.rb
@@ -0,0 +1,19 @@
+# Helper allows you to select value from filter-items
+#
+# Params
+# value - value for select
+# selector - css selector of item
+#
+# Usage:
+#
+# filter_item_select('Any Author', '.js-author-search')
+#
+module FilterItemSelectHelper
+ def filter_item_select(value, selector)
+ find(selector).click
+ wait_for_requests
+ page.within('.dropdown-content') do
+ click_link value
+ end
+ end
+end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index 879386b5437..4c88958264b 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -89,4 +89,25 @@ module LoginHelpers
})
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:saml]
end
+
+ def mock_saml_config
+ OpenStruct.new(name: 'saml', label: 'saml', args: {
+ assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback',
+ idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52',
+ idp_sso_target_url: 'https://idp.example.com/sso/saml',
+ issuer: 'https://localhost:3443/',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ })
+ end
+
+ def stub_omniauth_saml_config(messages)
+ Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
+ Rails.application.routes.disable_clear_and_finalize = true
+ Rails.application.routes.draw do
+ post '/users/auth/saml' => 'omniauth_callbacks#saml'
+ end
+ allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config)
+ stub_omniauth_setting(messages)
+ expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
+ end
end
diff --git a/spec/support/stub_env.rb b/spec/support/stub_env.rb
index 18597b5c71f..2999bcd9fb1 100644
--- a/spec/support/stub_env.rb
+++ b/spec/support/stub_env.rb
@@ -5,3 +5,11 @@ module StubENV
allow(ENV).to receive(:[]).with(key).and_return(value)
end
end
+
+# It's possible that the state of the class variables are not reset across
+# test runs.
+RSpec.configure do |config|
+ config.after(:each) do
+ @env_already_stubbed = nil
+ end
+end